Reputation: 75
I'm in the process of building a real-time chat app using React and socket to get to grips with the basics. Whenever a user sends a message, all other users receive this same message twice.
I'm using react-router to serve a join 'page' and a chat 'page'.
function App() {
return (
<div className="App">
<Router>
<Routes>
<Route path="/" element={<Join />} />
<Route path="/chat/:name" element={<Chat />} />
</Routes>
</Router>
</div>
);
}
In the chat page, which is where the messages are being rendered between users, I'm using the useEffect
hook however I am struggling to fully understand it.
function Chat () {
const [messages, addMessage] = useState([{
username: "chatbot",
time: new Date(),
message: "Welcome to the chatroom 😈"
}]);
const location = useLocation();
const [currentUser, updateUser] = useState(() => {
const user = location.pathname.split('/chat/')[1];
socket.emit("join_chat", {
username: user
})
return user;
});
useEffect(() => {
socket.on("receive_message", (data) => {
console.log('received message');
addMessage(prevMessages => {
return [...prevMessages, {
username: data.username,
time: new Date(),
message: data.message
}]
})
})
}, [socket])
const sendMessage = (e) => {
e.preventDefault();
socket.emit("send_message", {
message: currentMessage,
username: currentUser,
});
The console log is run twice on the receiving side every time a message is sent.
From what I understand, the hook allows us to define some code that will be run whenever there is a change to the DOM, so I'm unsure where this change is happening twice?
I'm also unsure as to why the socket.on
should live inside useEffect
in the first place. If useEffect
is only called immediately after a re-render, how is it still receiving every message via the socket?
Upvotes: 3
Views: 1375
Reputation: 202676
From what you describe it sounds like you are rendering the app into a React.StrictMode
component which runs certain lifecycle methods and functions twice as a way to help detect unintentional/unexpected side-effects.
The effect is run twice and the unintentional/unexpected side-effect is that the code has added two event handlers for the "receive_message"
event.
The useEffect
hook is missing returning a cleanup function to unsubscribe from the socket/message event when the component unmounts or the socket
dependency changes.
useEffect(() => {
const handleMessage = (data) => {
console.log('received message');
addMessage(prevMessages => {
return [...prevMessages, {
username: data.username,
time: new Date(),
message: data.message
}]
});
};
socket.on("receive_message", handleMessage);
return () => {
socket.off("receive_message", handleMessage);
};
}, [socket]);
From what I understand, the hook allows us to define some code that will be run whenever there is a change to the DOM
The useEffect
hook allows you to define some code that will run as a side-effect whenever the component renders for any reason. Reasons React components rerender are (1) Local state was updated, (2) a React prop was updated, (3) the parent component rerendered (for any of the same reasons).
I'm also unsure as to why the
socket.on
should live insideuseEffect
in the first place.
The "render" method in React components is to be considered a pure function. The entire function body of a React Function component is the "render" method. Subscribing to external resources is considered an "impure" action, i.e. it's a side-effect, and should only be subscribed to as part of the regular React component lifecycle.
Upvotes: 2