2

When using hooks in React and using an array as a state, I found that updating only one element of that state array with the setter function did not re-render the component. I was doing this:

const [listCollapsed, setListCollapse] = useState(Array(props.list.length).fill(false));

const expandCollapse = (ind) => {
    let newListCollapsed = listCollapsed;
    newListCollapsed[ind] = !listCollapsed[ind];
    setListCollapse(newListCollapsed);

}

where expandCollapse was a function called when pressing on a list element. I found that changing the first line of the function to:

let newListCollapsed = [...listCollapsed];

made it work. I was wondering what the explanation for that was.

3
  • 1
    the first way simply renames the variable, while still being the same reference. Second way actually creates a a new copy of the array similar to newListCollapsed = listCollapsed.slice(); thus creates a new immutable state object Commented Jan 14, 2020 at 8:12
  • 1
    In the first case, you’re just referencing the original array and mutating it, it’s still the same array so when React does its shallow equality check it assumes nothing has changed. In the second case you’re creating a new array that is a copy of the original. React checks and notices that it’s something different which triggers the rerender. Commented Jan 14, 2020 at 8:13
  • 1
    Side note: The usual convention is for the set function's name to exactly match the state variable's name, so setListCollapsed (with the d) rather than setListCollapse. (Of course, it's just convention, but following convention helps with understanding the code later, collaborating with others, ...) Commented Jan 14, 2020 at 8:15

1 Answer 1

9

Your first version broke one of the main React rules by modifying state directly (more on that in this part of the docs). The line

let newListCollapsed = listCollapsed;

just makes newListCollapsed and listCollapsed both refer to the same array (the one being used as state), it doesn't copy the array. When you do that, you end up with this:

state:Ref5461−−−−−−−−−−−−−−−−+
                              \      +−−−−−−−−−−−+ 
listCollapsed:Ref5461−−−−−−−−−−+−−−−>|  (array)  | 
                              /      +−−−−−−−−−−−+ 
newListCollapsed:Ref5461−−−−−+       | 0: false  | 
                                     | 1: false  | 
                                     | ...       | 
                                     +−−−−−−−−−−−+

So

setListCollapse(newListCollapsed);

doesn't do anything, because that's setting the same array that the state already contains. React doesn't see any change.

But this line:

let newListCollapsed = [...listCollapsed];

copies the array into a new array (using spread notation to spread out its entries into the new array created by the [] literal), so you have:

state:Ref5461−−−−−−−−−−−−−−−−+
                              \      +−−−−−−−−−−−+ 
listCollapsed:Ref5461−−−−−−−−−−+−−−−>|  (array)  | 
                                     +−−−−−−−−−−−+ 
                                     | 0: false  | 
                                     | 1: false  | 
                                     | ...       | 
                                     +−−−−−−−−−−−+

                                     +−−−−−−−−−−−+ 
newListCollapsed:Ref8465−−−−−−−−−−−−>|  (array)  | 
                                     +−−−−−−−−−−−+ 
                                     | 0: false  | 
                                     | 1: false  | 
                                     | ...       | 
                                     +−−−−−−−−−−−+

So when you call setListCollapse(newListCollapsed);, it's not the same thing, and the change gets made. That's the correct thing to do.

(The Ref#### values in those diagrams are conceptual. They're the object references for the two arrays. An object reference tells the JavaScript engine where the object is in memory. You never see the actual value of an object reference in your code.)

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.