Updating objects of key array pairs without mutation

31 views Asked by At

I used this code snippet inside one of my useEffect hooks. But it mutates the object and shows me the message to be appeared twice.

setConversations((prev) => {
  const oldConversation = room in prev ? prev[room] : [];
  const newConversation = [
    ...oldConversation,
    {
      sender: from,
      message: msg,
      time: new Date().toLocaleString(),
    },
  ];
  return { ...prev, [room]: newConversation };
});

What am I missing? Any suggestions would be much appreciated.

Edit:

The custom effect (where the message is received):

export const useSocketEventListener = (
  socket,
  peer,
  setPeersOnConference,
  setPeerIdsOnConference,
  setAvailableUsers,
  availableRooms,
  setAvailableRooms,
  setConferenceId,
  setCallOthersTriggered,
  setTransited,
  calls,
  setCalls,
  setConversations
) => {
  useEffect(() => {
    socket?.on('receiveMessage', (msg, from, room) => {
      // alert(`${msg} from ${from} room ${room}`);
      // let temp = socket.id === room ? from : room;
      setConversations((prev) => { // called twice (expected once to add the message to the state)
        const oldConversation = room in prev ? prev[room] : [];
        const newConversation = [
          ...oldConversation,
          {
            sender: from,
            message: msg,
            time: new Date().toLocaleString(),
          },
        ];
        console.log('calling once');
        return { ...prev, [room]: newConversation };
      });
      console.log('i fire once');
    });
    socket?.on('joinRoomAlert', (socketId, room) => {
      //
    });
    socket?.on('leaveRoomAlert', (socketId, room) => {
      alert(`${socketId} left the room ${room}`);
    });
    socket?.on('receivePeersOnConference', (peersOnConference) => {
      // setPeersOnConference(peersOnConference);
    });
    socket?.on('receiveData', (data) => {
      setAvailableUsers(data.users);
      setAvailableRooms(data.rooms);
    });
    socket?.on('peerEndCall', (peerId) => {
      calls[peerId]?.close();
      setCalls((prev) =>
        Object.keys(prev)
          .filter((key) => key !== peerId)
          .reduce((obj, key) => ({ ...obj, [key]: prev[key] }), {})
      );
      setPeersOnConference((prev) => {
        if (Object.keys(prev).length === 1) {
          setCallOthersTriggered(false);
          setTransited(false);
          return {};
        }
        return Object.keys(prev)
          .filter((key) => key !== peerId)
          .reduce((obj, key) => ({ ...obj, [key]: prev[key] }), {});
      });
    });
    socket?.on(
      'receiveCallOthersTriggered',
      (peerIds, conferenceId, caller) => {
        console.log('peerIds: ', peerIds);
        setConferenceId(socket.id === conferenceId ? caller : conferenceId);
        setCallOthersTriggered(true);
        setPeerIdsOnConference([...peerIds]);
      }
    );
    socket?.on('leaveCallAlert', (leftPeerId) => {
      //
    });
    socket?.emit('fetchData');
    peer?.on('call', async (call) => {
      try {
        setTransited(true);
        const selfStream = await getMedia();
        console.log(peer.id, selfStream);
        setPeersOnConference((prev) => ({ ...prev, [peer.id]: selfStream }));
        call.answer(selfStream);
        call.on('stream', (remoteStream) => {
          console.log(call.peer, remoteStream);
          setPeersOnConference((prev) => ({
            ...prev,
            [call.peer]: remoteStream,
          }));
        });
        call.on('close', () => {
          selfStream.getTracks().forEach((track) => track.stop());
        });
        call.on('error', (e) => console.log('error in peer call'));
      } catch (e) {
        console.log('error while receiving call');
      }
    });
  }, [socket, peer]);
};

The function to send message:

export const sendMessage = (socket, msg, to, setMessage, setConversations) => {
  socket.emit('sendMessage', msg, to);
  setMessage('');
  setConversations((prev) => { // called once
    const oldConversation = to in prev ? prev[to] : [];
    const newConversation = [
      ...oldConversation,
      {
        sender: socket.id,
        message: msg,
        time: new Date().toLocaleString(),
      },
    ];
    return { ...prev, [to]: newConversation };
  });
};

Note: Used the custom hook in the App.js file.

1

There are 1 answers

0
Rafi Sakib On BEST ANSWER

After googling for a while, I found a hack to handle double render in useEffect logic. There may exist other ways to solve my specific problem but the hack I found will do fine for me. As react-18 renders the useEffect hook twice in development mode, disabling the strict mode won't make the render once. For which we need to use a boolean variable to handle such cases.

useEffect(() => {
  let ignore = false;
  fetchStuff().then(res => {
    if (!ignore) setResult(res)
  })
  return () => { ignore = true }
}, [])

For more details, check out this blog post.

Note: The main problem was not about mutation. Updating function is working totally fine. Rendering the useEffect twice caused the error to be occured. :)