skip to Main Content

I have the following code :

import { useContext, useState, useEffect } from 'react';
import React from 'react';
import { RoomContext } from '../context/RoomContext';

let nextMessageID = 0

export default function Chat({roomId}) {
    const { socket } = useContext(RoomContext)

    const [messagesReceived, setMessagesReceived] = useState([]);

    useEffect(() => {
        socket.on('receive_message', (data) => {
            console.log(data.message);
            setMessagesReceived([
                ...messagesReceived,
                { id: nextMessageID++, content: data.message}
            ]);
        })
    }, [socket, messagesReceived])

    return (
        <div>
            <h1>Received messages :</h1>
            <ul>
                {messagesReceived.map(message => {
                    <li key={message.id}>{message.content}</li>
                })}
            </ul>
        </div>
    )
}

which is heavily inspired from the react documentation here : https://react.dev/learn/updating-arrays-in-state#adding-to-an-array

I can see that this code updates my state array "messagesReceived" correctly by adding a button that logs it to the console

<button onClick={() => {
    console.log(messagesReceived);
}}>Log messages</button>

but it does not update my component. How can I fix that ?

Also I’m not sure why I need to also execute my effect hook when "messagesReceived" changes but not doing so makes my array only contain the last message. I’d appreciate any explanation on this.

Thank you very much !

2

Answers


  1. In order to avoid creating a closure over the initial state of the messagesReceived array in the listener callback you can pass a callback to setMessagesReceived which will be passed the up-to-date state. see: React: Updating state based on the previous state. This will also allow you to remove messagesReceived from the dependency array.

    You should also always provide a ‘cleanup’ function in your useEffect in order to avoid adding multiple listeners. Here assigning the callback to a variable so that the same reference can be used in setting and removing the listener.

    Lastly, you need to be sure your map() callback is returning a value. see: When should I use a return statement in ES6 arrow functions

    let nextMessageID = 0;
    
    export default function Chat({ roomId }) {
      const { socket } = useContext(RoomContext);
    
      const [messagesReceived, setMessagesReceived] = useState([]);
    
      useEffect(() => {
        const receiveMessageCallback = (data) => {
          setMessagesReceived((prev) => [
            ...prev,
            { id: nextMessageID++, content: data.message },
          ]);
        };
    
        socket.on('receive_message', receiveMessageCallback);
    
        return () => {
          socket.removeListener('receive_message', receiveMessageCallback);
        };
      }, [socket]);
    
      return (
        <div>
          <h1>Received messages :</h1>
          <ul>
            {messagesReceived.map((message) => (
              <li key={message.id}>{message.content}</li>
            ))}
          </ul>
        </div>
      );
    }
    
    Login or Signup to reply.
  2. Add return in your map function.

    <ul>
         {messagesReceived.map(message => {
              return ( <li key={message.id}>{message.content}</li> )
           })}
      </ul>
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search