I’m was trying to ensure my knowledge of how React compares states and rerenders components and I’ve struggled to understand why this behaviour takes place:
I have simple component with two buttons and two state variables. Increment button is for incrementing counter, and Re-render button for triggering rerender. Counter state is an object with value property, which is an actual numeric counter. Trigger is just a boolean, which value is not used anywhere. Increment state update is done intentionally wrong just to check how the state is compared.
function App() {
const [counter, setCounter] = useState({
value: 0,
})
const [trigger, setTrigger] = useState(false)
console.log('Rendered ', Date.now())
console.log('State: ', {counter, trigger})
return (
<>
<h1>Object State</h1>
<p>Counter: {counter.value}</p>
<button onClick={() => {
counter.value++
setCounter(counter)
}}> Increment</button>
<button onClick={() => {
setTrigger(!trigger)
}}> Re-render</button>
</>
)
}
Clicking Increment does nothing as expected. When I click Re-render button, component is rerendered as expected and current counter value is shown. At this point all works as expected. But if after clicking Re-render, I click Increment, I see in console that the component was invoked, although displayed counter value wasn’t updated. It seems like the output is discarded. Clicking Increment after that does not have any effect and the component is not invoked. Why this unexpected invoke is happening and why it’s happening only once util next Re-render click?
I expect component to never invoke on Increment click. Actually it’s being invoked once on Increment click after Re-render click.
3
Answers
To fix this and ensure your component behaves as expected, you should avoid directly mutating the state. Instead, create a new object when you want to update counter:
This approach updates the state immutably by creating a new object with an updated value each time you click "Increment," ensuring React detects the change and re-renders the component appropriate
https://legacy.reactjs.org/docs/hooks-reference.html#bailing-out-of-a-state-update
In the legacy React doc, it is mentioned that React would run the component function even if the state value is not changed. This re-run of the component function would not run again if React discovers that the state stays the same.
State can hold any kind of JavaScript value, including objects. But you shouldn’t change objects that you hold in the React state directly. Instead, when you want to update an object, you need to create a new one (or make a copy of an existing one), and then set the state to use that copy. — React Official DOC
Or if you want to change the counter directly, you can use immer