skip to Main Content

This is my Component:

function Test() {
    const [data, setData]=useState<Array<string>>([]);
    console.log('Hello:', data);
    
    useEffect(()=>{
       console.log('data: ', data)
    }, [data])
    const test1 = () => {
        setData(data.concat('a'));
    }
    const test2 = () => {
        setData(data);
    }
    return (
        <>
           <button onClick={test1}>Button one</button>
           <button onClick={test2}>Button two</button>
       </>
    );
}

Everything works fine when clicking Button one. Component does re-render and effect does run. However, what happens with Button two is something I cannot explain:

  • If clicking Button two right after Button one, the Component does re-render but effect does not run. That makes no sense since React is using Object.is comparison for deciding if it should re-render/run the effect. How does this comparison produce different results among useState and useEffect? At first it decides to re-render, that means state value data has changed. How is that true with setData(data)? Then, it decides not to run the effect, that means there is no change in data dependency. Obviously, there is a contradiction among the above two decisions
  • If clicking Button two for a second time (after having clicked on Button one), then nothing happens which does make sense absolutely.

Could someone explain the above behaviour?

2

Answers


  1. If the new state is the same as the current state, as you mentioned by Object.is React will skip the re-render but as mentioned in the React useState docs React might still call the component.

    Although in some cases React may still need to call your component before skipping the children, it shouldn’t affect your code.

    This results in running the console.log with the "Hello: ", data values.

    And so, React does not actually re-render the component.

    We can see this with a useEffect with no dependency array which acccording to the React useEffect docs should run every re-render.

    Effect will re-run after every re-render of the component.

    useEffect(() => {
      console.warn("Render");
    });
    

    As you can see this is not the case.

    const {useState, useEffect, Fragment} = React;
    
    function Test() {
      const [data, setData] = useState([]);
      console.log("Hello:", data);
    
      useEffect(() => {
        console.warn("Render");
      });
    
      useEffect(() => {
        console.log("data: ", data);
      }, [data]);
      const test1 = () => {
        setData(data.concat("a"));
      };
      const test2 = () => {
        setData(data);
      };
      return (
        <Fragment>
          <button onClick={test1}>Button one</button>
          <button onClick={test2}>Button two</button>
        </Fragment>
      );
    }
    
    ReactDOM.createRoot(
        document.getElementById("root")
    ).render(
        <Test />
    );
    <div id="root"></div>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.development.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.development.js"></script>
    Login or Signup to reply.
  2. what happened is when you use setData(data); the Component Re-renders no matter if the Data has actually changed or not, but when it comes to useEffect the way it compares values it takes the old values and compares it with the new ones if the values have changed it will execute what inside it otherwise it won’t, this behavior will also happen if the data is an object it will check each value inside it to decide whether the object has changed or not

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