score:22

Accepted answer

Since you have written your useEffect to execute on initial mount of component, it creates a closure which references the initial value of messages and even if the messages update, it will still refer to the same value on subsequent calls

You should instead configure the useEffect to run on initial mount and messages change

export function useChat() {
  const [messages, setMessages] = useState([]);

  useEffect(() => {
    const socket = openSocket("http://localhost:3003");
    socket.on("chat message", msg => {
      const newState = update(messages, { $push: [msg] });
      setMessages(newState);
    });
  }, [messages]);

  return { messages };
} 

or else you could use the callback pattern to update state

export function useChat() {
  const [messages, setMessages] = useState([]);

  useEffect(() => {
    const socket = openSocket("http://localhost:3003");
    socket.on("chat message", msg => {
      setMessages(prevMessages => update(prevMessages, { $push: [msg] }););
    });
  }, []);

  return { messages };
}

score:1

As you are writing socket handler inside the useEffect() with an empty array, So this effect will run only once when your component will mount for the first time. The socket.on() function (or closure) will memorize the initial value of the messages and even if the messages gets change the socket.on() closure will still refer to its initial value. Solution for this problem will be to register our messages to the dependency array of effect.

export function useChat() {   
const [messages, setMessages] = 
useState([]);

useEffect(() => {
const socket = openSocket("http://localhost:3003");
socket.on("chat message", msg => {
  const newState = update(messages, { $push: [msg] });
  setMessages(newState);
});   }, [messages]);
return { messages }; 
}

Here a new problem you will encounter that each time messages get changed a new socket with "chat message" handler is created which may result unexpected and addition code to run multiple times. To solve that issue you will have to de-register the earlier handler. And I'll recommend you to create socket only once (e.g. inside App.js) and pass it as a props.

export function useChat(socket) {   
const [messages, setMessages] = useState([]);

useEffect(() => { 
socket.on("chat message", msg => {
  const newState = update(messages, { $push: [msg] });
  setMessages(newState);
});   
//De-register old handler   
 return function(){
socket.off("chat message")   } }, [messages]);

return { messages }; }

Related Query

More Query from same tag