Search code examples
reactjsdesign-patternsreact-hooksgatsby

Avoiding react component rerenders on all state changes


My <App /> component has 2 pieces of state. The first piece of state updates on user interaction and then triggers an update in the second piece of state using the useEffect() hook. This causes 2 re-renders anytime that the user clicks on the button. Can someone help me figure out how to only rerender <App /> after the second piece of state updates? Is this a bad react pattern?

Here is the codesandbox: https://codesandbox.io/s/wispy-butterfly-nm5fsr

You can view the console to see the re-renders.

The reason I need to fix this is because I have a real project where re-renders are happening and very large props are being passed between components. The re-renders are causing visible lag. I would share the entire project, but it's way too much for a stack overflow question.


Solution

  • It's rendering twice because you're updating state once per render, instead of updating both states at the same time. Consider what you're doing here:

    const handleClick = () => {
      if (state1) setState1((prev) => prev + 1);
      else setState1(1);
    };
    
    useEffect(() => {
      if (state1) setState2(state1 + 10);
    }, [state1]);
    

    This basically means:

    Update state1 and re-render. On that next render, update state2 and re-render.

    The calculation to derive the state updates isn't terribly complicated. Add 1 to a number, and add 10 to a number. You don't need to wait for the component to re-render to know what state1 will be. It will be the current value plus 1, which is easily calculated. You can use that calculation to update both states:

    const handleClick = () => {
      if (state1) {
        const newState1 = state1 + 1;
        setState1(newState1);
        setState2(newState1 + 10);
      } else {
        setState1(1);
        setState2(10);
      }
    };
    

    Or, even better, you don't need two state values in the first place. In this example, state2 is derived from state1 (and doing so is not a difficult or expensive calculation). Which means you're duplicating state.

    Don't duplicate state. Just derive the second value from state1:

    const [state1, setState1] = useState(null);
    
    const handleClick = () => {
      if (state1) setState1((prev) => prev + 1);
      else setState1(1);
    };
    
    return (
      <div className="App">
        {console.log("Rerendering children...")}
        <Child1 state1={state1} />
        <Child2 state2={state1 ? state1 + 10 : null} />
    
        <button type="button" onClick={handleClick}>
          Change State 1!
        </button>
      </div>
    );