简体   繁体   中英

Update component on socket.io event - ReactJS, Socket.io

I have a React chat app working with Socket.io. I can send messages from different sources but can't update every source that is connected to the socket. I tried to do it thought componentDidUpdate , using refs and etc to keep the socket, but nothing worked.

Socket:

const io = require('socket.io').listen(3002);
const jwt = require('jsonwebtoken');

const messages = [];

io.use((socket, next) => {
  if (socket.handshake.query && socket.handshake.query.token) {
    jwt.verify(
      socket.handshake.query.token,
      '4-8-15-16-23-42',
      (err, decoded) => {
        if (err) return next(new Error('Authentication error'));
        socket.decoded = decoded;
        next();
      }
    );
  } else {
    next(new Error('Authentication error'));
  }
}).on('connection', socket => {
  socket.emit('connected', {
    user: socket.decoded.username
  });

  socket.on('newMessage', message => {
    messages.push(message);
    socket.emit('messages', messages);
  });
});

Client:

import { useState, useEffect } from 'react';
import io from 'socket.io-client';

const token = sessionStorage.getItem('token');
const socket = io('http://localhost:3002', {
  query: { token }
});

const Chat = () => {
  const [user, setUser] = useState('');
  const [messages, setMessages] = useState([]);
  const [input, setInput] = useState('');

  useEffect(() => {
    socket.on('connected', msg => {
      setUser(msg.user);
    });
  }, []);

  const inputChangeHandler = e => setInput(e.target.value);

  const sendMessage = () => {
    socket.emit('newMessage', `${user}: ${input}`);
    socket.on('messages', newMessages => setMessages(newMessages));
    setInput('');
  };

  return (
    <div>
      <div id="message-container">
        <div>Greetings {user}!!!</div>
        {messages.map((message, index) => (
          <div key={index}>{message}</div>
        ))}
      </div>
      <input value={input} onChange={e => inputChangeHandler(e)} />
      <button onClick={sendMessage}>Send</button>
    </div>
  );
};

export default Chat;

You should connect to socket when component is mounted, and then attach message handler.
But the problem is that if you do it in useEffect, your handler want see state changes (your message array will be always empty as in initial state. That's because useEffect has empty array as second argument. It's something like chicken-egg problem. You can't use normal useState or useReducer so...

Here is solution:

First remove this:

    socket.on('messages', newMessages => setMessages(newMessages));

from sendMessage function.

We need some additional package.

yarn add immer use-immer
import React, {useEffect, useRef } from "react";
import io from 'socket.io-client';
import { useImmer } from "use-immer";

const Chat = () => {

  const [messages, setMessages] = useImmer({});

  useEffect(() => {

    const serverAddress = "https://my-chat-server";
    const socket = io( serverAddress,{
      transports: ['websocket']
    });

    socket.on("connect", () => {
      console.log("Connected");
      socketRef.current = socket;
    });

    socket.on("message", msg => {

      setMessages(msgs => {

        return msgs.concat(msg);

      });

    });


  },[setMessages]);

   return <div>Here you know what to do :)</div>
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM