I am following a tutorial on Scrimba for React and currently doing the Boxes challenge part 4.
I pretty much have a function that takes in an ID, and that ID relates to an array of key-value pairs.
const [squares, setSquares] = React.useState(boxes)
function toggle(id) {
/**
* Challenge: use setSquares to update the
* correct square in the array.
*
* Make sure not to directly modify state!
*
* Hint: look back at the lesson on updating arrays
* in state if you need a reminder on how to do this
*/
setSquares(oldSquares => {
let newSquares = oldSquares
newSquares[id - 1].on = !oldSquares[id - 1].on
return newSquares
})
}
Here is what squares contains:
[{id: 1, on: true}, {id: 2, on: false}, {id: 3, on: true}, {id: 4, on: true}, {id: 5, on: false}, {id: 6, on: false}]
My function above properly inverts the value for the on key and I prove this through print statements, however my component displaying the squares does not update even though the state is updating.
Anyone know why?
You're not creating a new array in state, but mutating the existing one. So the framework doesn't know there's a state update.
This creates a reference to the same array in memory:
let newSquares = oldSquares
This mutates a value in that array:
newSquares[id - 1].on = !oldSquares[id - 1].on
And this returns to state the same reference to the same array:
return newSquares
Don't mutate state. Instead, create a new array with the updated elements:
setSquares(oldSquares =>
oldSquares.map((s, i) =>
i === id - 1 ?
{ ...s, on: !s.on } :
s
)
)
In this case .map()
returns a new array reference, and within the .map()
callback we conditionally return a new object with the on
property negated if the index equals id - 1
or return the object as-is for all other indices.
For a much more verbose version to perhaps better illustrate the logic, it's effectively the same result as this:
setSquares(oldSquares => {
let newSquares = oldSquares.map((s, i) => {
if (i === id - 1) {
return { ...s, on: !s.on };
} else {
return s;
}
});
return newSquares;
})