简体   繁体   中英

React useState hook not consistently updating

I started integrating websockets into an existing React/Django app following along with this example (accompanying repo here ). In that repo, the websocket interface is in websockets.js , and is implemented in containers/Chat.js .

I can get that code working correctly as-is.

I then started re-writing my implementation to use Hooks, and hit a little wall. The data flows through the socket correctly, arrives in the handler of each client correctly, and within the handler can read the correct state. Within that handler, I'm calling my useState function to update state with the incoming data.

Originally I had a problem of my single useState function within addMessage() inconsistently firing (1 in 10 times?). I split my one useState hook into two (one for current message, one for all messages). Now in addMessage() upon receiving data from the server, my setAllMessages hook will only update the client where I type the message in - no other clients. All clients receive/can log the data correctly, they just don't run the setAllMessages function.

If I push to an empty array outside the function, it works as expected. So it seems like a problem in the function update cycle, but I haven't been able to track it down.

Here's my version of websocket.js :

class WebSocketService {
  static instance = null;

  static getInstance() {
    if (!WebSocketService.instance) {
      WebSocketService.instance = new WebSocketService();
    }
    return WebSocketService.instance;
  }

constructor() {
    this.socketRef = null;
    this.callbacks = {};
  }

  disconnect() {
    this.socketRef.close();
  }

  connect(chatUrl) {
    const path = `${URLS.SOCKET.BASE}${URLS.SOCKET.TEST}`;
    this.socketRef = new WebSocket(path);

    this.socketRef.onopen = () => {
      console.log('WebSocket open');
    };

    this.socketRef.onmessage = e => {
      this.socketNewMessage(e.data);
    };

    this.socketRef.onerror = e => {
      console.log(e.message);
    };

    this.socketRef.onclose = () => {
      this.connect();
    };
  }

  socketNewMessage(data) {
    const parsedData = JSON.parse(data);
    const { command } = parsedData;

    if (Object.keys(this.callbacks).length === 0) {
      return;
    }

    Object.keys(SOCKET_COMMANDS).forEach(clientCommand => {
      if (command === SOCKET_COMMANDS[clientCommand]) {
        this.callbacks[command](parsedData.presentation);
      }
    });
  }

  backend_receive_data_then_post_new(message) {
    this.sendMessage({
      command_for_backend: 'backend_receive_data_then_post_new',
      message: message.content,
      from: message.from,
    });
  }

  sendMessage(data) {
    try {
      this.socketRef.send(JSON.stringify({ ...data }));
    } catch (err) {
      console.log(err.message);
    }
  }



 addCallbacks(allCallbacks) {
    Object.keys(SOCKET_COMMANDS).forEach(command => {
      this.callbacks[SOCKET_COMMANDS[command]] = allCallbacks;
    });
  }

  state() {
    return this.socketRef.readyState;
  }
}

const WebSocketInstance = WebSocketService.getInstance();

export default WebSocketInstance;

And here's my version of Chat.js

export function Chat() {
  const [allMessages, setAllMessages] = useState([]);
  const [currMessage, setCurrMessage] = useState('');

  function waitForSocketConnection(callback) {
    setTimeout(() => {
      if (WebSocketInstance.state() === 1) {
        callback();
      } else {
        waitForSocketConnection(callback);
      }
    }, 100);
  }

  waitForSocketConnection(() => {
    const allCallbacks = [addMessage];
    allCallbacks.forEach(callback => {
      WebSocketInstance.addCallbacks(callback);
    });
  });


/*
 * This is the problem area
 * `incoming` shows the correct data, and I have access to all state
 * But `setAllMessages` only updates on the client I type the message into
 */
  const addMessage = (incoming) => {
    setAllMessages([incoming]);
  };

  // update with value from input
  const messageChangeHandler = e => {
    setCurrMessage(e.target.value);
  };

  // Send data to socket interface, then to server
  const sendMessageHandler = e => {
    e.preventDefault();
    const messageObject = {
      from: 'user',
      content: currMessage,
    };
    setCurrMessage('');
    WebSocketInstance.backend_receive_data_then_post_new(messageObject);
  };

  return (
    <div>
      // rendering stuff here
    </div>
  );
}

There is no need to rewrite everything into functional components with hooks.

You should decompose it functionally - main (parent, class/FC) for initialization and providing [data and] methods (as props) to 2 functional childrens/components responsible for rendering list and input (new message).

If you still need it... useEffect is a key... as all code is run on every render in functional components... including function definitions , redefinitions, new refs, duplications in callbacks array etc.

You can try to move all once defined functions into useEffect

useEffect(() => {

  const waitForSocketConnection = (callback) => {
    ...
  }

  const addMessage = (incoming) => {
    setAllMessages([incoming]);
  };

  waitForSocketConnection(() => {
    ...
  }

}, [] ); // <<< RUN ONCE

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