I'm developing a chat app using react-native, node, mongoose, socket.io, the problem is, when using socket.io, when I send message from user1 to user2, I'm able to receive the sent message from user1 but the useEffect
doesn't re-renders the component to add the new message into the array of messages
even though I have entered the correct dependencies. Below is the code for reference:
This is my MessageScreen & I have defined socket
outside the scope of functional component MessageScreen
:
const socket = io('ws://192.168.29.123:8080', {
transports: ['websocket'],
}); // declared outside the functional component
const [arrivalMessage, setArrivalMessage] = useState(null);
useEffect(() => {
socket.on('getMessage', data => {
console.log('received: ',data);
setArrivalMessage({
matchId: matchId,
senderId: data.senderId,
text: data.text,
createdAt: Date.now(),
updatedAt: Date.now(),
});
});
console.log('arrival msg: ',arrivalMessage);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [arrivalMessage, socket]);
useEffect(() => {
// ensures we don't get anyother user's msg
arrivalMessage &&
match.includes(arrivalMessage.senderId) &&
setMessages((prev) => [...prev, arrivalMessage]);
}, [arrivalMessage, match]);
useEffect(() => {
socket.emit('addUser', uid);
}, [uid]);
Although I'm receiving the correct data from my friend but the state is not updating & therefore I'm not able to display real-time messages. So, whenever I send a message from 1 user to another, this is my console output which confirms that I am able to receive the message from that user & obviously the title error:
LOG received: {"senderId": "61b5d1725a7ae2994", "text": {"matchId":"61b5d172511867ae298c", "senderId": "61b5d1725a7ae2994", "text": "me too!"}}
ERROR Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function. in MessageScreen (at SceneView.tsx:126)
LOG received: {"senderId": "61b5d1725a7ae2994", "text":{"matchId":"61b5d111867ae298c","senderId":"61b5d1725a7ae2994", "text": "me too!"}}
It would be really helpful if someone could point out what is wrong here! Thank you!
CodePudding user response:
You need to turn off the socket listener when the component unmounts
useEffect(() => {
const callback = data => {
console.log('received: ',data);
setArrivalMessage({
matchId: matchId,
senderId: data.senderId,
text: data.text,
createdAt: Date.now(),
updatedAt: Date.now(),
})
}
socket.on('getMessage', callback);
console.log('arrival msg: ',arrivalMessage);
// ADD THIS
return () => {
socket.off(event, callback)
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [arrivalMessage, socket]);
You also don't want the socket to run if not necessary. I would not initiate the socket out of the component, it may stay in the memory and cause memory leaks.
Better approach is to make a context where you initiate your socket and wrap your app/components that needs it. If your component unmounts, socket disconnects.
import SocketClient from 'socket.io-client'
export function useSocket(url) {
const [socket, setSocket] = useState(null)
useEffect(() => {
const io = SocketClient(url).connect()
setSocket(io)
return () => {
socket?.disconnect()
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
return socket
}
const SocketsContext = createContext({
socket: undefined,
})
function SocketsContextProvider({ children }) {
const socket = useSocket()
return (
<SocketsContext.Provider value={{ socket }}>
{children}
</SocketsContext.Provider>
)
}
export function useSocketEvent(event, callback) {
const { socket } = useContext(SocketsContext)
useEffect(() => {
if (!socket) {
return
}
socket.on(event, callback)
return () => {
socket.off(event, callback)
}
}, [callback, event, socket])
return socket
}
function ParentComponentWithSockets(){
return (
<SocketsContextProvider>
<YourComponent />
</SocketsContextProvider>
)
}
function YourComponent(){
const [arrivalMessage, setArrivalMessage] = useState(null);
useSocketEvent('getMessage', data => {
setArrivalMessage({
matchId: matchId,
senderId: data.senderId,
text: data.text,
createdAt: Date.now(),
updatedAt: Date.now(),
})
})
return (
//...
)
}