skip to Main Content

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

enter image description here

Can anyone explain how is this possible.

3

Answers


  1. Instead of use a variable, use useRef react hook

    import { useState, useRef } from "react";
    
    function PageTwo() {
    
    let [newName, setNewName] = useState("");
    let localPageTwoCount = useRef(0);
    
    const updateName = () =>  {
        console.log("localPageTwoCount", localPageTwoCount)
        localPageTwoCount.current = localPageTwoCount.current + 1;
        // You don't need to return anything from the onClick callback function
        setNewName("Name " + localPageTwoCount)
        
    }
    
    return <div>
        <h1>This content is from page2</h1>
        <div>newName {newName}</div>
        {/*This will show 0 unless there is re-render*/}
        <div>localPageTwoCount {localPageTwoCount}</div>
        <button onClick={updateName}> Update Count </button>
        
    </div>
    
    }
    
    export default PageTwo;
    

    useRef doesn’t trigger a state change nor does it re-render the components. You can use the useRef hook to store data which doesn’t require re-renders.

    For more info go through react useRef docs.

    Login or Signup to reply.
  2. 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:

    // localPageTwoCount is logged as 0
    localPageTwoCount++; //localPageTwoCount = 1;
    setNewName("Name " + localPageTwoCount) // newName is set to Name 1 and triggers a rerender
    

    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 and updateName is defined again.

    // localPageTwoCount is logged as 0
    localPageTwoCount++; //localPageTwoCount = 1;
    setNewName("Name " + localPageTwoCount) // newName is set to Name 1 and triggers a rerender.
    

    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:

    If you update a State Hook to the same value as the current state, React will bail out without rendering the children or firing effects. (React uses the Object.is comparison algorithm.)

    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 inside useEffect 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 older updateName has the old closed over (closures) value of localPageTwoCount which had incremented to 1.

    Now next time you click on the button:

    localPageTwoCount++; //localPageTwoCount=2
    return setNewName("Name " + localPageTwoCount); //sets newName to Name 2
    

    And this time there is a valid re-render. And hence, updateName is recreated again with the new value of localPageTwoCount which is 0.

    Login or Signup to reply.
  3. 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

    const [count, setCount] = useState(0);
    
    const update = () => {
    setCount(1);
    }
    
    useEffect(() => {
    if(count === 0)return;
    console.log("COUNT UPDATED")
    }, [count])
    
    

    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

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search