The below React code has a simple useState
hook that updates the newName
property. There is also a local variable localPageTwoCount
that is incremented inside the updateName
method.
import { useState } from "react";
function PageTwo() {
let [newName, setNewName] = useState("");
let localPageTwoCount = 0;
const updateName = () => {
console.log("localPageTwoCount", localPageTwoCount)
localPageTwoCount++;
return setNewName("Name " + localPageTwoCount)
}
return <div>
<h1>This content is from page2</h1>
<div>newName {newName}</div>
<div>localPageTwoCount {localPageTwoCount}</div>
<button onClick={updateName}> Update Count </button>
</div>
}
export default PageTwo;
My understanding is that when setName
is called it re-renders the component which re-initializes the variable localPageTwoCount
, making localPageTwoCount
always 0 in React.Dom. But the localPageTwoCount
inside updateName
method behaves weirdly it retains the incremented value and logs localPageTwoCount 1
for every 2 clicks.
This is how the console looks
Can anyone explain how is this possible.
3
Answers
Instead of use a variable, use
useRef
react hookuseRef
doesn’t trigger a state change nor does it re-render the components. You can use theuseRef
hook to store data which doesn’t require re-renders.For more info go through react useRef docs.
You will have to follow through the code to understand a bit.
In the first render:
localPageTwoCount
is 0;newName
is “.When you click on the button,
updateName
is called:The component rerenders because of state update. The value of
localPageTwoCount
becomes 0 again.We click the button again and the method is called again. The same thing as above happens but since this time
localPageTwoCount
is set to 0 andupdateName
is defined again.The answer lies here.
Why was there a rerender when the value was set to the same. This is because React could not figure out that there is no need for a rerender. This is documented here:
Note that React may still need to render that specific component again before bailing out. That shouldn’t be a concern because React won’t unnecessarily go “deeper” into the tree. If you’re doing expensive calculations while rendering, you can optimize them with useMemo.
But note that react does not go deep into the DOM tree. So which means that
Button
is not recreated on the DOM. Hence its event handlers remain the same.The optimization can be seen here in this sandbox. I have added a log in the render body, and a log inside a
useEffect
. Note that yolo insideuseEffect
is not logged the second time button is clicked.If the event handlers are the same, then it still refers to the older
updateName
. The olderupdateName
has the old closed over (closures) value oflocalPageTwoCount
which had incremented to 1.Now next time you click on the button:
And this time there is a valid re-render. And hence,
updateName
is recreated again with the new value oflocalPageTwoCount
which is 0.To understand this behaviour , you need to understand how state works
there are two categories of datatypes : Primitive (number, string, boolean) , Non Primitive (array, object)
primitives are passed by value and non-primitives are passed by reference
now when you call setState and pass it new value , it checks if new value is same as current value or not
if its different value , it updates state and re renders component
if its same value , it wont update state and do not re render component
for non primitive types state will always update as it can not compare array or object with == comparison
but in primitive types, it can
so what is happening in your case is that
-> on initial render
localCount = 0
name = ""
when you click button , new value for name will be "Name 1" which is different from current value (""), so component will re renders and re initialises
-> after 1st click
localCount = 0;
name = "Name 1"
now when you click again , the new value for name will be "Name 1" which is same as current value("Name 1"), here react will not update state as both values are same so after 2nd click the function will not be re initialised and will have old closure value of localCount, so values will be
-> after 2nd click
localCount = 1;
name = "Name 1";
now when you click again , the new value for name will be "Name 2" which is different from current value("Name 1"), so now it will update state and every thing goes to its initial state back again
that’s why every 3rd click you are getting increased value in console
to clearly understand it , try below example
here you will see that no matter how many times you call update() , you’ll only see console once as state only updates if passed value is different from current value