If you're using React Hooks you might encounter a scenario where your
useState
hook doesn't appear to be updating the value. This can occur
when you're mutating the state value in place and then calling setState
.
React performs a quick comparison so it will bail out of an update when
the object instance is the same which will result in a stale value.
Later on, if something else causes a rerender of the component you will suddenly see the fresh state leaving you scratching your head.
Consider the following example which illustrates the issue. It's a component
that adds a number to the array whenever the button is clicked. After
the button is clicked 3 times it should show: 1,2,3
.
import React, { useState } from 'react'
function Numbers() {
const [numbers, setNumbers] = useState([])
const addToNumbers = () => {
numbers.push(numbers.length)
setNumbers(numbers)
}
return (
<div className="App">
<div>{numbers.join(',')}</div>
<button onClick={addToNumbers}>Add number</button>
</div>
)
}
If you were to render this component and click the button three times you'd see that there is no number update happening.
Since addToNumbers
is mutating the state value directly it won't
be properly updated. If you avoid mutation and instead clone the
state array of numbers you will see rendering occur as you expect:
import React, { useState } from 'react'
function Numbers() {
const [numbers, setNumbers] = useState([])
const addToNumbers = () => {
setNumbers([...numbers, numbers.length])
}
return (
<div className="App">
<div>{numbers.join(',')}</div>
<button onClick={addToNumbers}>Add number</button>
</div>
)
}
I've created a CodeSandbox example that uses both methods of state setting with different buttons. It's interesting to see because you can click the array mutation version twice, and then the array mutation version, and then see the proper numbers render.
This is a result of the mutation version properly setting the new state. Though, React doesn't think a new value exists so it doesn't actually rerender.