2

I am having message listing, and appending live message received from socket, one object inserted from socket, but after that it always replace the last one instead of insert new.

const [chatList, setChatList] = useState(props.chatList ? props.chatList : []);

useEffect(() => {

    const messageListener = (message) => {
        console.log('message',message);
        console.log('chatList',chatList);

        if(message.conversation_id == props.conversation_id){

                const updatedMsgs = [...chatList,message];
                setChatList(updatedMsgs);
        }
        
    };
  
    socket.on('myEventName', messageListener);

    return () => {
      socket.off('myEventName', messageListener);
    };
  }, [props.conversation_id]);

New message and Messagelist log look like enter image description here

2 Answers 2

3

Looks like a stale enclosure of the chatList state. Use a functional state update to update from the previous state and not the state value closed over in callback scope, setChatList(list => [...list, message]);.

useEffect(() => {
  const messageListener = (message) => {
    console.log('message',message);
    console.log('chatList',chatList);

    if (message.conversation_id == props.conversation_id) {
      setChatList(list => [...list, message]);
    } 
  };

  socket.on('myEventName', messageListener);

  return () => {
    socket.off('myEventName', messageListener);
  };
}, [props.conversation_id]);
Sign up to request clarification or add additional context in comments.

Comments

0

This issue does occurs due to the Stale Closures in Javascript.

Hooks heavily rely on JavaScript closures. That's why hooks are so expressive and simple. But closures are sometimes tricky.

Here is the example:

function DelayedCount() {
  const [count, setCount] = useState(0);
  function handleClickAsync() {
    setTimeout(function delay() {
      setCount(count + 1);
    }, 1000);
  }
  return (
    <div>
      {count}
      <button onClick={handleClickAsync}>Increase async</button>
    </div>
  );
}

If you quickly click the button 2 times, then it increase the count by 1 only, instead of 2.

On each click setTimeout(delay, 1000) schedules the execution of delay() after 1 second. delay() captures the variable count as being 0.

Both delay() closures (because 2 clicks have been made) update the state to the same value: setCount(count + 1) = setCount(0 + 1) = setCount(1).

All because the delay() closure of the second click has captured the outdated count variable as being 0.

To fix the problem, We need to use a functional way setCount(count => count + 1) to update count state:

...
function handleClickAsync() {
  setTimeout(function delay() {
    setCount(count => count + 1);
  }, 1000);
}
...

Now the button does work as expected.

Here is the complete article which may help you to understand about Stale Closures. Article Link

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.