Skip to content

React Hooks useState updating an array #15041

Closed
@brettshollenberger

Description

@brettshollenberger

Do you want to request a feature or report a bug?
Hooks Clarification

What is the current behavior?
I'm trying to understand the lifecycle of useState.

I have a mock application using a mock websocket. Every second, it sends a new message from the backend. The new message is meant to be appended to an array using useState.

Here are a few different examples that highlight my confusion:

Example 1
In this example, if I set the websocket's onmessage function once in useEffect, then whenever I call setMessages to update the messages array, the messages array I receive as an input is empty.

const [messages, setMessages] = useState([]);

function receiveMsg(msg) {
  setMessages(messages.concat(JSON.parse(msg.data)));
}

useEffect(function() {
    if (_.isUndefined(socket)) {
      let ws = new MockWebsocket("ws://127.0.0.1:8080/");
      ws.onmessage = receiveMsg;
    }
});

The effect of this is that I only ever get the latest message in my array.

Example 2
If, however, I set the onmessage function on every render as in this example, then I get my full array with data appended as I would expect.

const [messages, setMessages] = useState([]);

function receiveMsg(msg) {
  setMessages(messages.concat(JSON.parse(msg.data)));
}

if (!_.isUndefined(socket)) {
  socket.onmessage = receiveMsg;
}

useEffect(function() {
  if (_.isUndefined(socket)) {
    let ws = new MockWebsocket("ws://127.0.0.1:8080/");
    ws.onmessage = receiveMsg;
  }
});

In the receiveMessage function, my messages array is the whole array instead of an empty one in this example.

Example 3
BUT, if I assign a new reference to messages, as in this example, and re-assign the value inside receiveMsg, then I don't have to re-assign the onmessage function over and over.

const [messages, setMessages] = useState([]);
let msgRef = messages;

function receiveMsg(msg) {
  msgRef = msgRef.concat(JSON.parse(msg.data));
  setMessages(msgRef);
}

useEffect(function() {
  if (_.isUndefined(socket)) {
    let ws = new MockWebsocket("ws://127.0.0.1:8080/");
    ws.onmessage = receiveMsg;
  }
});

Example 4
But, if I assign a new reference and don't re-assign to it, as in this example, I continue ending up with an empty array. This suggests it's the assignment back to msgRef that is keeping the entire array within the closure.

const [messages, setMessages] = useState([]);
let msgRef = messages;

function receiveMsg(msg) {
  setMessages(msgRef.concat(JSON.parse(msg.data)));
}

What is the expected behavior?
My original expectation was that example #1 would work. I can tell there's something I'm not totally understanding about the way the assignment of variables to closure works with this hook, but I'm struggling to define what exactly's going on. Can someone shed some light on why this works this way?

Which versions of React, and which browser / OS are affected by this issue? Did this work in previous versions of React?

React 16.8

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions