I have a simple react component that sends and receives messages via SocketIO. My state hooks look like this
const [newMessage, createMessage] = useState('');
const [messages, setMessage] = useState([]);
const [isConnected, setConnection] = useState(false);
The idea is that the user can compose and send the message through the socket and then will receive the message back. The user inputs some text into the text area. The contents of the message are saved to the "newMessage" local state variable - implementation details below if necessary - and then the user sends the newMessage through the socket.
const sendMessage = () => {
const currentMessage = {...newMessage}; //reduced for the sake of brevity
socket.emit('message', currentMessage);
createMessage('');
};
const composeMessage = (
<>
<div>
<textarea rows="2" cols="28" placeholder="Chat Message" onKeyDown={(e) => handleKeyDown(e)} onChange={(e) => createMessage(e.currentTarget.value)} value={newMessage} />
</div>
<div>
<button onClick={sendMessage}><i className="ra ra-horn-call" /></button>
</div>
</>
);
Serverside, the message is received and immediately transmitted back to the room. I put the function that listens for the message response into the useEffect hook. The idea was to use the hook in place of componentDidMount, so I originally implemented it by passing an empty array as the second argument to useEffect. When I did this, any time a message was received, it would appear to clear out my "messages" state and then replace the contents with the new message. From the browser, it looked like there was only ever one message that could be held in state at a time - the previous message kept getting replaced. I tired passing the "messages" state in the second argument array and the message now appeared to get appended to the state and the component re-rendered as it was received -cool right, thats what I wanted?
I noticed that I was having performance issues on every subsequent message and ultimately discovered that the socket listener was getting re-applied each render. In order to stop this a colleague suggested that I add a boolean to state that should prevent the socket from getting re-added whenever there is a re-render. I updated the component and the result was the same as it was the first time when I was not passing the messages state into the second argument - it kept replacing the first message in state and did not append new messages. I just want the listener to update the local state and rerender the messages any time a new message comes through the socket. I am kind of at a loss for how to do this with hooks. Anyone have any ideas?
The final iteration of the useEffect hook that I wrote is below.
useEffect(() => {
debugger
if (!isConnected) {
socket.on('message', (message) => {
const nextState = messages.slice();
nextState.push(message);
setMessage(nextState);
debugger
});
setConnection(true);
}
}, [messages, setConnection]);