Cannot read properties of undefined (reading 'socketId') - node.js

This is Messenger.jsx page
import "./messenger.css";
import Topbar from "../../components/topbar/Topbar";
import Conversation from "../../components/conversations/Conversation";
import Message from "../../components/message/Message";
import ChatOnline from "../../components/chatOnline/ChatOnline";
import { useContext, useEffect, useRef, useState } from "react";
import { AuthContext } from "../../context/AuthContext";
import axios from "axios";
import { io } from "socket.io-client";
export default function Messenger() {
const [conversations, setConversations] = useState([]);
const [currentChat, setCurrentChat] = useState(null);
const [messages, setMessages] = useState([]);
const [newMessage, setNewMessage] = useState("");
const [arrivalMessage, setArrivalMessage] = useState(null);
const [onlineUsers, setOnlineUsers] = useState([]);
const socket = useRef();
const { user } = useContext(AuthContext);
const scrollRef = useRef();
const client = axios.create({
baseURL: "http://localhost:8800/api"
});
useEffect(() => {
socket.current = io("ws://localhost:8900");
socket.current.on("getMessage", (data) => {
setArrivalMessage({
sender: data.senderId,
text: data.text,
createdAt: Date.now(),
});
});
}, []);
useEffect(() => {
arrivalMessage &&
currentChat?.members.includes(arrivalMessage.sender) &&
setMessages((prev) => [...prev, arrivalMessage]);
}, [arrivalMessage, currentChat]);
useEffect(() => {
socket.current.emit("addUser", user._id);
socket.current.on("getUsers", (users) => {
setOnlineUsers(
user.followings.filter((f) => users.some((u) => u.userId === f))
);
});
}, [user]);
useEffect(() => {
const getConversations = async () => {
try {
const res = await client.get("/conversations/" + user._id);
setConversations(res.data);
} catch (err) {
console.log(err);
}
};
getConversations();
}, [user._id]);
useEffect(() => {
const getMessages = async () => {
try {
const res = await client.get("/messages/" + currentChat?._id);
setMessages(res.data);
} catch (err) {
console.log(err);
}
};
getMessages();
}, [currentChat]);
const handleSubmit = async (e) => {
e.preventDefault();
const message = {
sender: user._id,
text: newMessage,
conversationId: currentChat._id,
};
const receiverId = currentChat.members.find(
(member) => member !== user._id
);
socket.current.emit("sendMessage", {
senderId: user._id,
receiverId,
text: newMessage,
});
try {
const res = await client.post("/messages", message);
setMessages([...messages, res.data]);
setNewMessage("");
} catch (err) {
console.log(err);
}
};
useEffect(() => {
scrollRef.current?.scrollIntoView({ behavior: "smooth" });
}, [messages]);
return (
<>
<Topbar />
<div className="messenger">
<div className="chatMenu">
<div className="chatMenuWrapper">
<input placeholder="Search for friends" className="chatMenuInput" />
{conversations.map((c) => (
<div onClick={() => setCurrentChat(c)}>
<Conversation conversation={c} currentUser={user} />
</div>
))}
</div>
</div>
<div className="chatBox">
<div className="chatBoxWrapper">
{currentChat ? (
<>
<div className="chatBoxTop">
{messages.map((m) => (
<div ref={scrollRef}>
<Message message={m} own={m.sender === user._id} />
</div>
))}
</div>
<div className="chatBoxBottom">
<textarea
className="chatMessageInput"
placeholder="write something..."
onChange={(e) => setNewMessage(e.target.value)}
value={newMessage}
></textarea>
<button className="chatSubmitButton" onClick={handleSubmit}>
Send
</button>
</div>
</>
) : (
<span className="noConversationText">
Open a conversation to start a chat.
</span>
)}
</div>
</div>
<div className="chatOnline">
<div className="chatOnlineWrapper">
<ChatOnline
onlineUsers={onlineUsers}
currentId={user._id}
setCurrentChat={setCurrentChat}
/>
</div>
</div>
</div>
</>
);
}
This is the error iam getting:
a user connected.
a user connected.
D:\MYPROJECTS\cfg\mern tutorials\chatapp\socket\index.js:35
io.to(user.socketId).emit("getMessage", {
^
TypeError: Cannot read properties of undefined (reading 'socketId')
I have created a node app to initiate socket. In the index.js file of socket-app i made all connections. Iam having socket error, it is saying socket app crashed.
index.js page:
const io = require("socket.io")(8900, {
cors: {
origin: "http://localhost:3000",
},
});
let users = [];
const addUser = (userId, socketId) => {
!users.some((user) => user.userId === userId) &&
users.push({ userId, socketId });
};
const removeUser = (socketId) => {
users = users.filter((user) => user.socketId !== socketId);
};
const getUser = (userId) => {
return users.find((user) => user.userId === userId);
};
io.on("connection", (socket) => {
//when ceonnect
console.log("a user connected.");
//take userId and socketId from user
socket.on("addUser", (userId) => {
addUser(userId, socket.id);
io.emit("getUsers", users);
});
//send and get message
socket.on("sendMessage", ({ senderId, receiverId, text }) => {
const user = getUser(receiverId);
io.to(user.socketId).emit("getMessage", {
senderId,
text,
});
});
//when disconnect
socket.on("disconnect", () => {
console.log("a user disconnected!");
removeUser(socket.id);
io.emit("getUsers", users);
});
});

from this socket.on("sendMessage", ({ senderId, receiverId, text }) => {
const user = getUser(receiverId);
io.to(user.socketId).emit("getMessage", {
senderId,
text,
});
});
to
socket.on("sendMessage", ({ senderId, receiverId, text }) => {
const user = getUser(receiverId);
io.to(user?.socketId).emit("getMessage", {
senderId,
text,
});
});

Related

Error Cannot read property '_id' of null?

It's been all day I tried to update a user profile but I keep getting an error Cannot read property _id null. I can't figure out whats the problem is. I've checked many times backend and frontend, Even checking other's Github repository but I cannot figure out where is the problem. Please help me out with this. Here is my code
And please is there any better way to fetch the user if I don't fetch from userInfo please tell me
if you want to check the full repository here is the GitHub link
Here is what I see in the console (
PUT /api/users/profile 500 62.771 ms - 642
Profile.js
import { useEffect, useState } from 'react';
import { Button, Col, Container, Form, InputGroup, Row } from 'react-bootstrap';
import { toast, ToastContainer } from 'react-toastify';
import { useDispatch, useSelector } from 'react-redux';
import { updateUserProfile } from '../actions/userActions';
// import { USER_UPDATE_PROFILE_RESET } from '../constant/userConstants';
const Profile = ({ history }) => {
const [name, setName] = useState('');
const [email, setEmail] = useState('');
const [pic, setPic] = useState();
const [password, setPassword] = useState('');
const [confirmPassword, setConfirmPassword] = useState('');
const [picMessage, setPicMessage] = useState();
const [passwordType, setPasswordType] = useState('password');
const [passwordType2, setPasswordType2] = useState('password');
const [showPass, setShowPass] = useState(false);
const [showPass2, setShowPass2] = useState(false);
const dispatch = useDispatch();
// const userDetails = useSelector((state) => state.userDetails);
// const { user } = userDetails;
// console.log(` this is from line 25 ${user}`);
const userLogin = useSelector((state) => state.userLogin);
const { userInfo } = userLogin;
const userUpdateProfile = useSelector((state) => state.userUpdateProfile);
const { success } = userUpdateProfile;
useEffect(() => {
if (!userInfo) {
history.push('/login');
} else {
setName(userInfo.name);
setEmail(userInfo.email);
setPic(userInfo.pic);
}
if (success) {
return toast.success('Updated Successfully');
}
showPass ? setPasswordType('text') : setPasswordType('password');
showPass2 ? setPasswordType2('text') : setPasswordType2('password');
}, [showPass, showPass2, dispatch, history, userInfo, success]);
const postDetails = (pics) => {
setPicMessage(null);
if (pics.type === 'image/jpeg' || pics.type === 'image/png') {
const data = new FormData();
data.append('file', pics);
data.append('upload_preset', 'codeblogger_profile_image');
data.append('cloud_name', 'dhuej17x0');
fetch('https://api.cloudinary.com/v1_1/dhuej17x0/image/upload', {
method: 'post',
body: data,
})
.then((res) => res.json())
.then((data) => {
setPic(data.secure_url.toString());
})
.catch((err) => {
toast.error(err);
});
} else {
setPicMessage('Please Select an Image');
toast.error(picMessage);
}
};
const submitHandler = (e) => {
e.preventDefault();
dispatch(updateUserProfile({ name, email, password, pic }));
};
return (
<div className="profilePage mt-4 py-3">
<ToastContainer />
<Container>
<h2>PROFILE</h2>
<Row className="profileContainer">
<Col md={6}>
<Form onSubmit={submitHandler}>
<Form.Group controlId="name" className="mb-2">
<Form.Label>Name</Form.Label>
<Form.Control
type="name"
value={name}
placeholder="Enter Name"
onChange={(e) => setName(e.target.value)}
/>
</Form.Group>
<Form.Group controlId="email" className="mb-2">
<Form.Label>Email</Form.Label>
<Form.Control
type="email"
value={email}
placeholder="Enter email"
onChange={(e) => setEmail(e.target.value)}
/>
</Form.Group>
<Form.Group controlId="password" className="mb-2">
<Form.Label>New Password</Form.Label>
<InputGroup>
<Form.Control
type={passwordType}
placeholder="New Password"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
<InputGroup.Text>
<i
onClick={() => setShowPass(!showPass)}
className={showPass ? 'fas fa-eye-slash' : 'fas fa-eye'}
style={{ cursor: 'pointer' }}></i>
</InputGroup.Text>
</InputGroup>
</Form.Group>
<Form.Group controlId="confirmPassword" className="mb-2">
<Form.Label>Confirm Password</Form.Label>
<InputGroup>
<Form.Control
type={passwordType2}
placeholder="Confirm Password"
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
/>
<InputGroup.Text>
<i
onClick={() => setShowPass2(!showPass2)}
className={showPass2 ? 'fas fa-eye-slash' : 'fas fa-eye'}
style={{ cursor: 'pointer' }}></i>
</InputGroup.Text>
</InputGroup>
</Form.Group>
<Form.Group controlId="pic" className="mb-2">
<Form.Label>Change Profile Picture</Form.Label>
<Form.File
onChange={(e) => postDetails(e.target.files[0])}
id="custom-file"
type="image/png"
custom
/>
</Form.Group>
<Button
type="submit"
variant="success"
style={{ letterSpacing: '2px' }}>
UPDATE
</Button>
</Form>
</Col>
<Col
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}}>
<img src={pic} alt={name} className="profilePic" />
</Col>
</Row>
</Container>
</div>
);
};
export default Profile;
Here is userAction.js file
// User Details Action Start===========================================================================
export const getUserDetails = (id) => async (dispatch, getState) => {
try {
dispatch({
type: USER_DETAILS_REQUEST,
});
const {
userLogin: { userInfo },
} = getState();
const config = {
headers: {
Authorization: `Bearer ${userInfo.token}`,
},
};
const { data } = await axios.get(`/api/users/${id}`, config);
dispatch({
type: USER_DETAILS_SUCCESS,
payload: data,
});
} catch (error) {
const message =
error.response && error.response.data.message
? error.response.data.message
: error.message;
if (message === 'Not authorized, token failed') {
dispatch(logout());
}
dispatch({
type: USER_DETAILS_FAIL,
payload: toast.error(message),
});
}
};
// User Details Action Complete===========================================================================
// User Profile Details Action Start===========================================================================
export const updateUserProfile = (user) => async (dispatch, getState) => {
try {
dispatch({
type: USER_UPDATE_PROFILE_REQUEST,
});
const {
userLogin: { userInfo },
} = getState();
const config = {
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${userInfo.token}`,
},
};
const { data } = await axios.put(`/api/users/profile`, user, config);
dispatch({
type: USER_UPDATE_PROFILE_SUCCESS,
payload: data,
});
dispatch({
type: USER_LOGIN_SUCCESS,
payload: data,
});
localStorage.setItem('userInfo', JSON.stringify(data));
} catch (error) {
const message =
error.response && error.response.data.message
? error.response.data.message
: error.message;
if (message === 'Not authorized, token failed') {
dispatch(logout());
}
dispatch({
type: USER_UPDATE_PROFILE_FAIL,
payload: toast.error(message),
});
}
};
// User Profile Details Action Complete===========================================================================
Backend Usercontroller.js file
export const updateUserProfile = asyncHandler(async (req, res) => {
const user = await User.findById(req.user._id);
if (user) {
user.name = req.body.name || user.name;
user.email = req.body.email || user.email;
user.pic = req.body.pic || user.pic;
if (req.body.password) {
user.password = req.body.password;
}
const updatedUser = await user.save();
res.json({
_id: updatedUser._id,
name: updatedUser.name,
email: updatedUser.email,
pic: updateUser.pic,
isAdmin: updatedUser.isAdmin,
token: generateToken(updatedUser._id),
});
} else {
res.status(404);
throw new Error('User not found');
}
});
routes.js file
import express from 'express';
import {
adminUpdateUserProfile,
deleteUser,
getUserById,
getUserProfile,
getUsers,
loginUser,
registerUser,
updateUserProfile,
} from '../controller/userController.js';
import { admin, protect } from '../middleware/authMiddleware.js';
const router = express.Router();
router.route('/').post(registerUser).get(protect, admin, getUsers);
router.route('/login').post(loginUser);
router
.route('/profile')
.get(protect, getUserProfile)
.put(protect, updateUserProfile);
router
.route('/:id')
.delete(protect, admin, deleteUser)
.get(protect, admin, getUserById)
.put(protect, admin, adminUpdateUserProfile);
export default router;

express server with socket.io does not emit to right recipient

I am trying to create a video chat and using express and socket.io on the backend and react and simple-peer on the frontend.
This is my server code:
const http = require('http');
const express = require('express');
const app = express();
const httpServer = http.createServer(app);
app.use(require('cors')());
const io = require('socket.io')(httpServer, {
cors: {
origin: ['http://localhost:3000', 'http://localhost:3001'],
method: ['GET', 'POST']
}
});
const cache = {};
io.on('connection', socket => {
socket.on('join', id => {
cache[id] = socket.id;
console.log('cache', cache);
socket.join(id);
});
socket.on('disconnect', () => {
socket.broadcast.emit('callEnded');
});
socket.on('callUser', data => {
console.log('calling user', data.userToCall, cache[data.userToCall]);
io.to(data.userToCall).emit('callUser', {
signal: data.signalData,
from: data.from,
name: data.name
});
});
// socket.on('answerCall', data => {
// console.log('data', data);
// io.to(cache[data.to]).emit('callAccepted', data.signal);
// });
socket.on('answerCall', data => {
console.log('answering call', data);
io.to(data.to).emit('callAccepted', data.signal);
});
});
httpServer.listen(4000, () => 'Listening...');
I am accepting requests from port 3000 and 3001 because that is where I am running my two apps. As I don't have a login system on the backend at the moment, I am running one app for each peer.
The code for the first app looks like this:
import { useEffect, useState, useRef } from "react";
import Peer from "simple-peer";
import io from "socket.io-client";
import './App.css';
const ID = 'fey'
function App() {
const [ stream, setStream ] = useState()
const [receivingCall, setReceivingCall] = useState(false) ;
const [caller, setCaller] = useState("") ;
const [callerSignal, setCallerSignal] = useState() ;
const [callAccepted, setCallAccepted] = useState(false);
const [callEnded, setCallEnded] = useState(false);
const socket = io.connect("http://localhost:4000/");
const myVideo = useRef()
const userVideo = useRef()
const connectionRef = useRef()
useEffect(() => {
navigator.mediaDevices.getUserMedia({video: true, audio: true})
.then((stream)=>{
setStream(stream)
myVideo.current.srcObject = stream
})
.catch((err) => console.log(err))
socket.emit('join', ID)
socket.on("callUser", (data)=>{
setReceivingCall(true)
setCaller(data.from)
setCallerSignal(data.signal)
})
}, [])
const callUser = ()=> {
const peer = new Peer({
initiator:true,
trickle:false,
stream:stream
})
peer.on("signal", (data)=>{
socket.emit("callUser",{
userToCall: 'fey-clone',
signalData: data,
from: ID,
name: "Fey"
})
})
peer.on("stream", (stream)=> {
userVideo.current.srcObject = stream
})
socket.on("callAccepted", (signal) => {
console.log('call accepted!!')
setCallAccepted(true)
peer.signal(signal)
})
connectionRef.current = peer
}
const answerCall = () => {
console.log('peer exists')
setCallAccepted(true)
const peer = new Peer({
initiator:false,
trickle:false,
stream: stream
})
peer.on("signal", (data)=> {
console.log('call answered')
socket.emit("answerCall", {signal:data, to: caller})
})
peer.on("stream", (stream) =>{
userVideo.current.srcObject = stream
})
peer.signal(callerSignal)
connectionRef.current = peer
}
const leaveCall = ()=>{
setCallEnded(true)
connectionRef.current.destroy()
}
return (
<div className="App">
<div className="video">
{stream && <video playsInline muted ref={myVideo} autoPlay style={{width: "300px", height: "300px" }} />}
{callAccepted && <video playsInline muted ref={userVideo} autoPlay style={{width: "300px", height: "300px" }} />}
</div>
{callAccepted && !callEnded ? (
<button onClick={leaveCall}>
End Call
</button>
):(
<button onClick={callUser}>call meeee</button>
)}
{receivingCall && !callAccepted ? (
<div className="caller">
<h1>Fey is calling ...</h1>
<button onClick={answerCall} >
Answer
</button>
</div>
) : null
}
</div>
);
}
export default App;
The code for the other peer looks unsurprisingly similar, but the ID is different and the userToCall is the other one.
import { useEffect, useState, useRef } from "react";
import Peer from "simple-peer";
import io from "socket.io-client";
import './App.css';
const ID = 'fey-clone'
function App() {
const [ stream, setStream ] = useState()
const [receivingCall, setReceivingCall] = useState(false) ;
const [caller, setCaller] = useState("") ;
const [callerSignal, setCallerSignal] = useState() ;
const [callAccepted, setCallAccepted] = useState(false);
const [callEnded, setCallEnded] = useState(false);
const socket = io.connect("http://localhost:4000/");
const myVideo = useRef()
const userVideo = useRef()
const connectionRef = useRef()
useEffect(() => {
navigator.mediaDevices.getUserMedia({video: true, audio: true})
.then((stream)=>{
setStream(stream)
myVideo.current.srcObject = stream
})
.catch((err) => console.log(err))
socket.emit('join', ID)
socket.on("callUser", (data)=>{
setReceivingCall(true)
setCaller(data.from)
setCallerSignal(data.signal)
})
}, [])
const callUser = ()=> {
const peer = new Peer({
initiator:true,
trickle:false,
stream:stream
})
peer.on("signal", (data)=>{
socket.emit("callUser",{
userToCall: 'fey',
signalData: data,
from: ID,
name: "fey clone"
})
})
peer.on("stream", (stream)=> {
userVideo.current.srcObject = stream
})
socket.on("callAccepted", (signal) => {
console.log('call accepted!!')
setCallAccepted(true)
peer.signal(signal)
})
connectionRef.current = peer
}
const answerCall = () => {
setCallAccepted(true)
const peer = new Peer({
initiator:false,
trickle:false,
stream: stream
})
peer.on("signal", (data)=> {
console.log(data, caller)
socket.emit("answerCall", {signal:data, to: caller})
})
peer.on("stream", (stream) =>{
console.log('I streeeam')
userVideo.current.srcObject = stream
})
peer.signal(callerSignal)
connectionRef.current = peer
}
const leaveCall = ()=>{
setCallEnded(true)
connectionRef.current.destroy()
}
return (
<div className="App">
<div className="video">
{stream && <video playsInline muted ref={myVideo} autoPlay style={{width: "300px", height: "300px" }} />}
{callAccepted && <video playsInline muted ref={userVideo} autoPlay style={{width: "300px", height: "300px" }} />}
</div>
{callAccepted && !callEnded ? (
<button onClick={leaveCall}>
End Call
</button>
):(
<button onClick={callUser}>call meeee</button>
)}
{receivingCall && !callAccepted ? (
<div className="caller">
<h1>Fey is calling ...</h1>
<button onClick={answerCall} >
Answer
</button>
</div>
) : null
}
</div>
);
}
export default App;
The call seems to be going through the right recipient. When fey calls fey-clone, fey-clone can accept the call. The signal seems to work fine as well. However, it seems that the original caller fey never receives the event callAccepted from the server, so the video call cannot start. It appears with most probability that the server does not emit the event to the right peer, but I tried to debug to no avail. Is there something I am missing here?
It looks like the problem lies on the connection from the client.
The connection is happening inside the component, which means a new connection will be created every time the component re-renders. Bad mistake.
My code on the client looks like this now:
import React,{ useEffect, useRef, useState } from "react";
import Peer from "simple-peer";
import io from "socket.io-client";
const socket = io.connect('http://localhost:5000')
function Chat() {
const [ stream, setStream ] = useState()
const [ receivingCall, setReceivingCall ] = useState(false)
const [ caller, setCaller ] = useState("")
const [ callerSignal, setCallerSignal ] = useState()
const [ callAccepted, setCallAccepted ] = useState(false)
const [ idToCall, setIdToCall ] = useState("")
const [ callEnded, setCallEnded] = useState(false)
const [ name, setName ] = useState("")
const myVideo = useRef()
const userVideo = useRef()
const connectionRef= useRef()
useEffect(() => {
navigator.mediaDevices.getUserMedia({ video: true, audio: true })
.then((stream) => {
setStream(stream)
myVideo.current.srcObject = stream
})
socket.emit('join', 'fey')
socket.on("callUser", (data) => {
setReceivingCall(true)
setCaller(data.from)
setName(data.name)
setCallerSignal(data.signal)
})
}, [])
const callUser = (id) => {
const peer = new Peer({
initiator: true,
trickle: false,
stream: stream
})
peer.on("signal", (data) => {
socket.emit("callUser", {
userToCall: 'fey-clone',
signalData: data,
from: 'fey',
name: 'fey'
})
})
peer.on("stream", (stream) => {
userVideo.current.srcObject = stream
})
socket.on("callAccepted", (signal) => {
setCallAccepted(true)
peer.signal(signal)
})
connectionRef.current = peer
}
const answerCall =() => {
setCallAccepted(true)
const peer = new Peer({
initiator: false,
trickle: false,
stream: stream
})
peer.on("signal", (data) => {
socket.emit("answerCall", { signal: data, to: caller })
})
peer.on("stream", (stream) => {
userVideo.current.srcObject = stream
})
peer.signal(callerSignal)
connectionRef.current = peer
}
const leaveCall = () => {
setCallEnded(true)
connectionRef.current.destroy()
}
return (
<>
<h1>zoomish</h1>
<div className="container">
<div className="video-container">
<div className="video">
{stream && <video playsInline muted ref={myVideo} autoPlay style={{width: "300px" }} />}
</div>
<div className="video">
{callAccepted && !callEnded ?
<video playsInline ref={userVideo} autoPlay style={{width:"300px"}}/>
: null}
</div>
</div>
<div className="myId">
<div className="call-button">
{callAccepted && !callEnded ? (
<button onClick={leaveCall}>End Call</button>
):(
<button onClick={()=>callUser(idToCall)}>call me</button>
)}
</div>
</div>
<div>
{receivingCall && !callAccepted ? (
<div className="caller">
<h1>{name} is calling ...</h1>
<button onClick={answerCall}>Answer</button>
</div>
):null}
</div>
</div>
</>
);
}
export default Chat;
On the server nothing is particularly wrong, but I replicating the room that is already assigned to a socket once it connects with socket.join, and that does not need to be done. So I moved to maintaining a map of userId and socket.id. In a production app, this would probably be delegate to a separate storage for better handling the traffic. My server looks like this now:
const express = require("express")
const http = require("http")
const app = express()
const server = http.createServer(app)
const io = require("socket.io")(server, {
cors: {
origin: ["http://localhost:3000", "http://localhost:3001" ],
methods: [ "GET", "POST" ]
}
})
let users = {}
io.on("connection", (socket) => {
socket.on('join', (userId) => {
users[userId] = socket.id
});
socket.on("disconnect", () => {
socket.broadcast.emit("callEnded")
})
socket.on("callUser", (data) => {
io.to(users[data.userToCall]).emit("callUser", {
signal: data.signalData,
from: data.from,
name: data.name
})
})
socket.on("answerCall", (data) => {
io.to(users[data.to]).emit("callAccepted", data.signal)
})
})
server.listen(5000, () => console.log("server is running on port 5000"))
And that's it!

Browsers hangs while using Socket.io with ReactJS and ExpressJS

Problem:
I am working on a chat application. When I send more than 9-10 requests, the browser slows down and eventually, it just hangs. On refreshing the page, everything is back to normal.
I searched the socket.io documentation but couldn't get any solution regarding this matter.
Code:
Here is my Backend Express.JS code:
index.js
const express = require("express");
const { addUser, getUser, deleteUser, getAllUsers } = require("./users_api");
const app = express();
const server = require("http").createServer(app);
const io = require("socket.io")(server, {
cors: {
origin: "http://localhost:3000",
methods: ["GET", "POST"],
},
});
const port = 5000 || process.env.PORT;
io.on("connection", (socket) => {
socket.on("join", ({ name, room }, callback) => {
const { err, user } = addUser({ id: socket.id, name, room });
if (err) return callback(err);
if (user) {
socket.emit("message", {
user: "admin",
text: `${user.name} has entered the room ${user.room}.`,
});
socket.broadcast.to(user.room).emit("message", {
user: "admin",
text: `${user.name} has joined the room.`,
});
socket.join(user.room);
io.to(user.room).emit("users", {
room: user.room,
users: getAllUsers(user.room),
});
callback();
}
});
socket.on("client_message", (msg) => {
const user = getUser(socket.id);
if (user) io.to(user.room).emit("message", { user: user.name, text: msg });
});
socket.on("disconnect", () => {
console.log(6);
const user = deleteUser(socket.id);
if (user) {
io.to(user.room).emit("message", {
user: "admin",
text: `${user.name} has left the room.`,
});
io.to(user.room).emit("users", {
room: user.room,
users: getAllUsers(user.room),
});
}
});
});
server.listen(port, () => console.log(`Server started at port ${port}.`));
users_api.js:
const current_users = [];
const addUser = ({ id, name, room }) => {
name = name.trim().toLowerCase().split(" ").join("_");
room = room.trim().toLowerCase();
const exist_user = current_users.find(
(user) => name === user.name && room === user.room
);
if (exist_user)
return {
err: "User with this name already exists.",
};
current_users.push({ id, name, room });
return { user: { id, name, room } };
};
const deleteUser = (id) => {
const index = current_users.findIndex((user) => user.id === id);
if (index !== -1) return current_users.splice(index, 1)[0];
};
const getUser = (id) => current_users.find((user) => user.id === id);
const getAllUsers = (room) =>
current_users.filter((user) => user.room === room);
module.exports = { addUser, deleteUser, getUser, getAllUsers };
Front-end Code:
import io from "socket.io-client";
import React, { useEffect, useRef, useState } from "react";
import queryString from "query-string";
import "./ChatPage.css";
const END_POINT = "http://localhost:5000";
const ChatPage = (props) => {
const [message, setMessage] = useState("");
const [messages, setMessages] = useState([]);
const [users, setUsers] = useState([]);
const socket = useRef(null);
const handleSubmit = () => {
socket.current.emit("client_message", message);
setMessage("");
};
useEffect(() => {
const { name, room } = queryString.parse(props.location.search);
socket.current = io(END_POINT);
socket.current.emit("join", { name, room }, (error) => {
if (error) alert(error);
});
return () => {
socket.current.disconnect();
socket.current.off();
};
}, [props.location.search, END_POINT]);
useEffect(() => {
socket.current.on("message", (msg) => {
setMessages([...messages, msg]);
});
socket.current.on("users", ({ users }) => {
setUsers(users);
});
}, [messages.length]);
return (
<div className="chat-room-container">
<div className="chat-room-left">
<ul>
{messages.map((message, i) => (
<li key={i}>
{message.name}
{message.text}
</li>
))}
</ul>
<input
type="text"
value={message}
onChange={(e) => setMessage(e.target.value)}
/>
<button type="button" onClick={handleSubmit}>
Submit
</button>
</div>
<div className="chat-room-right">
<ul>
{users.map((user, i) => (
<li key={i}>{user.name}</li>
))}
</ul>
</div>
</div>
);
};
export default ChatPage;
I don't understand what's wrong, please help.
Instead of sending a message on event or broadcast try to send list of messages to particular user or all users list with their messages belonging to same room. This will leads to less render in frontend.
For example,
Your Frontend code will change like,
useEffect(() => {
socket.current.on("message", (msgList) => {
setMessages(msgList);
});
socket.current.on("users", ({ users }) => {
setUsers(users);
});
}, [messages.length]);
Also if different rooms then there occurs a problem to filter the messages. So to solve this there are two possible ways:
When you send filter data from server.
Filter data in client according to user name and room name.
As this solution is not a good practice as every time the whole list of message will come and applying filter on that will increase time complexity. But you can reduce to some extent by making proper structure of data state on server.
I think this will help you.
You are creating new socket connection on every useEffect, so after ten messages, you have ten connections.
socket = io(END_POINT);
I store the created socket connection in useRef - so if it is created again, it is overwritten with the new one - and it does not duplicate.
const socketConection = useRef(null)
useEffect(() => {
socketConnection.current = io(END_POINT)
}, [deps])
Of course, in all following uses you have to use socketConection.current

Builds a chat app in React, using axios and firestore

Please I would be happy if anyone would help me
I have a problem, I can not use the server-side functions, I call the functions with axios, and execute it in react hooks.
I actually build chat, which is why I use react hook, because I want messages to be updated all the time.
I also use firestore. There I save the messages, and receive them through the server side function.
It's a component of the chat - it's causing me problems, I do not understand why.
The server side functions work great, I tested them in postman, and they worked. The problem is that I can't run them in a function component. I do not know what I'm doing wrong.
The error I get here is in the line chat.users.length> 0?, When I make this comparison I get that chat.users is undefined, but I do not understand why because I initialize it at first, using a server side function , Which gives the necessary information
I'm very confused, and I'm new here on the site, I'm trying to figure out why it has not worked for two whole days
I think I might be confused by syntax, for example using an unnecessary dispatch inside component of the chat
i got this error:
enter image description here
component of the chat
import React, { useEffect, useState } from 'react';
import './style.css';
import { useDispatch, useSelector } from 'react-redux';
import { getRealtimeUsers, updateMessage, getRealtimeConversations } from '../../redux/actions/chatActions';
import { Fragment } from 'react';
const User = (props) => {
const { chat, onClick } = props;
return (
<div onClick={() => onClick(chat)} className="displayName">
<div className="displayPic">
<img src="https://i.pinimg.com/originals/be/ac/96/beac96b8e13d2198fd4bb1d5ef56cdcf.jpg" alt="" />
</div>
<div style={{ display: 'flex', flex: 1, justifyContent: 'space-between', margin: '0 10px' }}>
<span style={{ fontWeight: 500 }}>{chat.firstName} {chat.lastName}</span>
<span className={chat.isOnline ? `onlineStatus` : `onlineStatus off`}></span>
</div>
</div>
);
}
const HomePage = (props) => {
const dispatch = useDispatch();
const user = useSelector(state => state.user.credentials);
const chat = useSelector(state => state.chat);
const [chatStarted, setChatStarted] = useState(false);
const [chatUser, setChatUser] = useState('');
const [message, setMessage] = useState('');
const [userUid, setUserUid] = useState(null);
let unsubscribe;
useEffect(() => {
//unsubscribe = dispatch(getRealtimeUsers(user.handle))
dispatch(getRealtimeUsers());
}, []);
//console.log(user);
//componentWillUnmount
useEffect(() => {
return () => {
//cleanup
//unsubscribe.then(f => f()).catch(error => console.log(error));
unsubscribe.then(f => f()).catch(error => console.log(error));
}
}, []);
//function
const initChat = (chat) => {
setChatStarted(true)
setChatUser(`${chat.firstName} ${chat.lastName}`)
setUserUid(chat.handle);
console.log(chat);
dispatch(getRealtimeConversations({ uid_1: user.handle, uid_2: chat.handle }));
}
const submitMessage = (e) => {
const msgObj = {
user_uid_1: user.handle,
user_uid_2: userUid,
message
}
if (message !== "") {
dispatch(updateMessage(msgObj))
.then(() => {
setMessage('')
});
}
//console.log(msgObj);
}
return (
<Fragment>
<section className="container">
<div className="listOfUsers">
{console.log(chat)}
{
//chat.users != undefined
chat.users.length > 0 ?
chat.users.map(user => {
return (
<User
onClick={initChat}
key={user.handle}
user={user}
/>
);
})
: null
}
</div>
<div className="chatArea">
<div className="chatHeader">
{
chatStarted ? chatUser : ''
}
</div>
<div className="messageSections">
{
chatStarted ?
chat.conversations.map(con =>
<div style={{ textAlign: con.user_uid_1 == user.handle ? 'right' : 'left' }}>
<p className="messageStyle" >{con.message}</p>
</div>)
: null
}
</div>
{
chatStarted ?
<div className="chatControls">
<textarea
value={message}
onChange={(e) => setMessage(e.target.value)}
placeholder="Write Message"
/>
<button onClick={submitMessage}>Send</button>
</div> : null
}
</div>
</section>
</Fragment>
);
}
export default HomePage;
This is the axios:
app.get('/realtimeUsers', FBAuth, getRealtimeUsers );
app.post('/updateMessage', FBAuth, updateMessage);
app.get('/realtimeConversations', FBAuth, getRealtimeConversations);
And this is the server side functions - They work 100% - I checked them many times and they worked.:
const { db } = require('../util/admin');
exports.getRealtimeUsers = (req, res) => {
db.collection("users")
.onSnapshot((querySnapshot) => {
const users = [];
querySnapshot.forEach(function (doc) {
if (doc.data().handle != req.user.handle) {
users.push(doc.data());
}
});
return res.json(users);
});
}
exports.updateMessage = (req, res) => {
db.collection('conversations')
.add({
...req.body,
isView: false,
createdAt: new Date()
})
.then(() => {
return res.json({ message: "Conversations added successfully" });
})
.catch((err) => {
console.error(err);
return res.status(500).json({ error: err.code });
});
}
exports.getRealtimeConversations = (req, res) => {
console.log(JSON.stringify("testing"));
console.log(JSON.stringify(req.query));
console.log(JSON.parse(req.query.user));
console.log(JSON.parse(req.query.user).uid_1);
console.log(JSON.parse(req.query.user).uid_2);
db.collection('conversations')
.where('user_uid_1', 'in', [JSON.parse(req.query.user).uid_1, JSON.parse(req.query.user).uid_2])
.orderBy('createdAt', 'asc')
.onSnapshot((querySnapshot) => {
const conversations = [];
querySnapshot.forEach(doc => {
console.log(JSON.stringify(doc));
if (
(doc.data().user_uid_1 == JSON.parse(req.query.user).uid_1 && doc.data().user_uid_2 == JSON.parse(req.query.user).uid_2)
||
(doc.data().user_uid_1 == JSON.parse(req.query.user).uid_2 && doc.data().user_uid_2 == JSON.parse(req.query.user).uid_1)
) {
conversations.push(doc.data())
}
});
console.log(conversations);
return res.json(conversations);
})
//return res.json([]);
}
this is the actions that used in the client side, here i call to the axios:
import { userConstants } from "../types";
import axios from 'axios';
export const getRealtimeUsers = () => (dispatch) => {
dispatch({ type: `${userConstants.GET_REALTIME_USERS}_REQUEST` });
axios
.get('/realtimeUsers')
.then((res) => {
console.log(res);
dispatch({
type: `${userConstants.GET_REALTIME_USERS}_SUCCESS`,
payload: res.data
});
})
.catch((err) => console.log(err))
}
export const updateMessage = (msgObj) => (dispatch) => {
axios.post('/updateMessage', msgObj)
.then(() => { })
.catch((err) => console.log(err));
}
export const getRealtimeConversations = (user) => (dispatch) => {
//user = { uid_1: "from visualcode", uid_2: "userUid" };
console.log(JSON.stringify(user));
axios.get('/realtimeConversations',
{
params: {
user: JSON.stringify(user)
//uid_1:JSON.stringify("user.handle"),
//uid_2:JSON.stringify("userUid")
}
}
)
.then((res) => {
dispatch({
type: userConstants.GET_REALTIME_MESSAGES,
payload: res.data
});
})
.catch((err) => console.log(err))
}
I am not able to understand your whole code flow, i.e., how the chat.users will be populated before initChat is called.
But still, for your problem, you should always put a check for undefined values while iterating through an array.
<div className="listOfUsers">
{console.log(chat)}
{
//chat.users != undefined
chat && chat.users && chat.users.length > 0 &&
chat.users.map(user => {
return (
<User
onClick={initChat}
key={user.handle}
user={user}
/>
);
})
}
</div>

_id is missing after doing actions

i'm currently creating my first MERN App, and everything is going well, until something happened, and i'm going my try to explain because i need help !
What i'm doing is a facebook clone, where you can post something, you can delete your post and you can update your post, the logic is simple, i call dispatch to pass the data to the actions, the actions pass the data to the backend, and the backend return something to me and it saves in my store, because i'm using redux
The problem is that, when i have 2 post, and i want to delete a post, or maybe i want to edit it, the other post dissapears, it's like it loses its id and then loses the information, then i can't do anything but reaload the page, and it happens always
this is how it looks like, everything fine
Then, after trying to edit a post, the second one lost its information, and in the console, it says that Warning: Each child in a list should have a unique "key" prop, and i already gave each post the key={_id}, but the post lost it and i don't know how
Here's the code
Posts.js
import React, { useState } from "react";
import "./Posts.css";
import moment from "moment";
// Icons
import { BiDotsVertical, BiLike } from "react-icons/bi";
import { MdDeleteSweep } from "react-icons/md";
import { AiFillLike } from "react-icons/ai";
import { GrClose } from "react-icons/gr";
// Calling actions
import { deletePost, } from "../actions/posts.js";
// Gettin The Data From Redux
import { useSelector, useDispatch } from "react-redux";
const Posts = ({ setCurrentId }) => {
const [animation, setAnimation] = useState(false);
const [modal, setModal] = useState(false);
const [modalPost, setModalPost] = useState({});
// Getting The Posts
const posts = useSelector(state => state.posts);
const dispatch = useDispatch();
// Showing And Hiding Modal Window
const ModalWindow = post => {
setModalPost(post);
setModal(true);
};
// Liking the post
// const Like = id => {
// dispatch(giveLike(id));
// setAnimation(!animation);
// };
if (!posts.length) {
return <div>Loading</div>;
} else {
return (
<div className="Posts">
{/* // Modal window for better look to the post */}
{/* {modal && (
<div className="modalWindow">
<div className="container">
<div className="container-image">
<img src={modalPost.image} alt="" />
</div>
<div className="information">
<div className="container-information">
<div className="data-header">
<h2>
User <br />{" "}
<span style={{ fontWeight: "400" }}>
{moment(modalPost.createdAt).fromNow()}
</span>
</h2>
<span className="data-icon" onClick={() => setModal(false)}>
<GrClose />
</span>
</div>
<div className="message">
<h2>{modalPost.title}</h2>
<p>{modalPost.message}</p>
</div>
</div>
</div>
</div>
</div>
)} */}
{/* */}
{posts.map(post => {
const { _id, title, message, image, createdAt, likes } = post;
return (
<div className="Posts-container" key={_id}>
<div className="Fit">
<div className="Fit-stuff">
<h2 className="Fit-stuff_title">
User <br />{" "}
<span style={{ fontWeight: "400" }}>
{moment(createdAt).fromNow()}
</span>
</h2>
<a
className="Fit-stuff_edit"
href="#form"
onClick={() => setCurrentId(_id)}
>
<BiDotsVertical />
</a>
</div>
<div className="Fit-data">
<h2 className="Fit-data_title">{title}</h2>
<p className="Fit-data_message">{message}</p>
{image ? (
<div className="Fit-img">
<img
onClick={() => ModalWindow(post)}
src={image}
alt=""
/>
</div>
) : (
<div></div>
)}
</div>
<div className="Fit-shit">
<span>
{animation ? (
<AiFillLike className="fullLightBlue" />
) : (
<BiLike />
)}
{likes}
</span>
<span onClick={() => dispatch(deletePost(_id))}>
<MdDeleteSweep />
</span>
</div>
</div>
</div>
);
})}
</div>
);
}
};
export default Posts;
The form where i call update and create Post
import React, { useState, useEffect } from "react";
import Filebase from "react-file-base64";
// For the actions
import { useDispatch, useSelector } from "react-redux";
import { createPost, updatePost } from "../actions/posts.js";
import {
Wrapper,
FormContainer,
Data,
DataInput,
SecondDataInput,
FormContainerImg,
FormContainerButtons,
Buttons
} from "./FormStyled.js";
const Form = ({ currentId, setCurrentId }) => {
const [formData, setFormData] = useState({
title: "",
message: "",
image: ""
});
const specificPost = useSelector(state =>
currentId ? state.posts.find(p => p._id === currentId) : null
);
// Sending The Data And Editing The data
const dispatch = useDispatch();
useEffect(() => {
if (specificPost) setFormData(specificPost);
}, [specificPost]);
// Clear Inputs
const clear = () => {
setCurrentId(0);
setFormData({ title: "", message: "", image: "" });
};
const handleSubmit = async e => {
e.preventDefault();
if (currentId === 0) {
dispatch(createPost(formData));
clear();
} else {
dispatch(updatePost(currentId, formData));
clear();
}
};
return (
<Wrapper>
<FormContainer onSubmit={handleSubmit}>
<Data>
<DataInput
name="title"
maxLength="50"
placeholder="Title"
type="text"
value={formData.title}
onChange={e => setFormData({ ...formData, title: e.target.value })}
/>
<SecondDataInput
name="message"
placeholder="Message"
maxLength="300"
value={formData.message}
required
onChange={e =>
setFormData({ ...formData, message: e.target.value })
}
/>
<FormContainerImg>
<Filebase
required
type="file"
multiple={false}
onDone={({ base64 }) =>
setFormData({ ...formData, image: base64 })
}
/>
</FormContainerImg>
<FormContainerButtons>
<Buttons type="submit" create>
{specificPost ? "Edit" : "Create"}
</Buttons>
<Buttons onClick={clear} clear>
Clear
</Buttons>
</FormContainerButtons>
</Data>
</FormContainer>
</Wrapper>
);
};
export default Form;
My actions
import {
GETPOSTS,
CREATEPOST,
DELETEPOST,
UPDATEPOST,
LIKEPOST
} from "../actionTypes/posts.js";
import * as api from "../api/posts.js";
export const getPosts = () => async dispatch => {
try {
const { data } = await api.getPosts();
dispatch({ type: GETPOSTS, payload: data });
} catch (error) {
console.log(error);
}
};
export const createPost = newPost => async dispatch => {
try {
const { data } = await api.createPost(newPost);
dispatch({ type: CREATEPOST, payload: data });
} catch (error) {
console.log(error);
}
};
export const updatePost = (id, updatePost) => async dispatch => {
try {
const { data } = await api.updatePost(id, updatePost);
dispatch({ type: UPDATEPOST, payload: data });
} catch (error) {
console.log(error);
}
};
export const deletePost = id => async dispatch => {
try {
await api.deletePost(id);
dispatch({ type: DELETEPOST, payload: id });
} catch (error) {
console.log(error);
}
};
Redux Part
import {
GETPOSTS,
CREATEPOST,
DELETEPOST,
UPDATEPOST,
LIKEPOST
} from "../actionTypes/posts.js";
const postData = (posts = [], action) => {
switch (action.type) {
case GETPOSTS:
return action.payload;
case CREATEPOST:
return [...posts, action.payload];
case UPDATEPOST:
return posts.map(post =>
action.payload._id === post._id ? action.payload : posts
);
case DELETEPOST:
return posts.filter(post => post._id !== action.payload);
default:
return posts;
}
};
export default postData;
My controllers in the backend
import mongoose from "mongoose";
import infoPost from "../models/posts.js";
// Getting All The Posts
export const getPosts = async (req, res) => {
try {
const Posts = await infoPost.find();
res.status(200).json(Posts);
} catch (error) {
res.status(404).json({ message: error.message });
console.log(error);
}
};
// Creating A Post
export const createPost = async (req, res) => {
const { title, message, image } = req.body;
const newPost = new infoPost({ title, message, image });
try {
await newPost.save();
res.status(201).json(newPost);
} catch (error) {
res.status(409).json({ message: error.message });
console.log(error);
}
};
// Update A Post
export const updatePost = async (req, res) => {
const { id } = req.params;
const { title, message, image } = req.body;
if (!mongoose.Types.ObjectId.isValid(id))
return res.status(404).send(`No Post With Id Of ${id}`);
const updatedPost = { title, message, image, _id: id };
await infoPost.findByIdAndUpdate(id, updatedPost, { new: true });
res.json(updatedPost);
};
// Deleting A Post
export const deletePost = async (req, res) => {
const { id } = req.params;
if (!mongoose.Types.ObjectId.isValid(id))
return res
.status(404)
.send(`We Couldnt Found The Post With Id Of ${id} To Delete`);
await infoPost.findByIdAndRemove(id);
res.json(`Post With Id Of ${id} Deleted Succesfully`);
};
// Liking A Post
export const likePost = async (req, res) => {
const { id } = req.params;
if (!mongoose.Types.ObjectId.isValid(id))
return res.status(404).send(`No post with id: ${id}`);
const post = await infoPost.findById(id);
const updatedPost = await infoPost.findByIdAndUpdate(
id,
{ likeCount: post.likeCount + 1 },
{ new: true }
);
res.json(updatedPost);
};
Even though i've been trying to solve this problem for nearly 3.5 hours, i think that the problem might be in my Posts.js part, if you can help me, you're the greatest !

Resources