question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

React Hooks useState updating an array

See original GitHub issue

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

Issue Analytics

  • State:closed
  • Created 5 years ago
  • Comments:8 (1 by maintainers)

github_iconTop GitHub Comments

157reactions
gaearoncommented, Mar 6, 2019

Function components capture the values that they rendered with. That includes effects capturing state that belongs to that render. So what happens in your initial example is that:

  • First render has messages set to [].
  • Effect from first render initializes the socket and binds it to receiveMsg from first render — which itself sees messages from first render ([]).
  • Effect from second render exits early. Your socket is still the one set up in first render.
  • Effect from third render exits early. Your socket is still the one set up in first render.
  • Effect from fourth render exits early. Your socket is still the one set up in first render.
  • And so on.

Any time new message is received, the socket handling it is the one created in first render. That socket uses receiveMsg from first render which itself sees messages from the first render.


So the root of the problem is that you don’t actually want receiveMsg to use the state from the render it belongs to. You want to enqueue an update based on its current state. That’s what the “updater form” of setState does:

setMessages(msgs => msgs.concat(JSON.parse(msg.data)));

That works correctly: https://codesandbox.io/s/x7x24oxlzz.


As a side note, socket isn’t really “state”. It’s a mutable object that you want to lazily create. A ref would be more idiomatic: https://codesandbox.io/s/q8yl2z4pm6.

Hope this helps!

0reactions
wulinjie122commented, Sep 23, 2020

thanks , resolved it, help me to much @gaearon

Read more comments on GitHub >

github_iconTop Results From Across the Web

Update Arrays with React useState Hook Without Push
In this article, we'll look at how to update arrays using the React useState() hook and without the Array object's push() method.
Read more >
How to store and update arrays in React useState hook
Tutorial on how to add, update and delete an array stored in react state, created using useState. Illustrates different ways to update the ......
Read more >
Updating Arrays in State - React Docs
Updating Arrays in State ... Arrays are mutable in JavaScript, but you should treat them as immutable when you store them in state....
Read more >
How do I update states `onChange` in an array of object in ...
This will modify the value of the state and update the useState hook if its an array of string. Share.
Read more >
React hooks - working with state (array of objects)
Since useState returns an array we are able to destructure the current state value and a function that lets you update the state....
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found