Dan Abramov

If state updates slowly

  • Ensure that the program is running in production
  • Make sure state is not placed higher than it needs to be
  • Use the React development tool and other methods to detect the cause of secondary rendering
  • Wrap useMemo around high-overhead subtrees or wherever you need it

You can do without useMemo

In the following example, when the color in the App changes, we will re-render the component that we have manually delayed greatly.

import { useState } from 'react';

export default function App() {
  let [color, setColor] = useState('red');
  return (
    <div>
      <input value={color} onChange={(e)= > setColor(e.target.value)} />
      <p style={{ color}} >Hello, world!</p>
      <ExpensiveTree />
    </div>
  );
}

function ExpensiveTree() {
  let now = performance.now();
  while (performance.now() - now < 100) {
    // Artificial delay -- do nothing for 100ms
  }
  return <p>I am a very slow component tree.</p>;
}

Copy the code

This can be done using useMemo, but here are two ways to do this without using useMemo

1. Move State down

Take a closer look at the rendering code and you’ll notice that only a portion of the returned tree really cares about the current color:

let [color, setColor] = useState('red'); ... <input value={color} onChange={(e) = > setColor(e.target.value)} />
<p style={{ color}} >Hello, world!</p>
Copy the code

So let’s extract this into the Form component and move the state into it:

export default function App() {
  return (
    <>
      <Form />
      <ExpensiveTree />
    </>
  );
}

function Form() {
  let [color, setColor] = useState('red');  return (
    <>
      <input value={color} onChange={(e)= > setColor(e.target.value)} />      
      <p style={{ color}} >Hello, world!</p>
    </>
  );
}
Copy the code

2. Content improvement

This solution doesn’t work when a portion of state is used in code above a high-overhead tree. For example, if we put color inside the parent div:

export default function App() {
  let [color, setColor] = useState('red');
  return (
    <div style={{ color}} >   
      <input value={color} onChange={(e)= > setColor(e.target.value)} />
      <p>Hello, world!</p>
      <ExpensiveTree />
    </div>
  );
}
Copy the code

What to do? We can split the App component into two child components. The code that depends on color goes into the ColorPicker component along with the color State variable. The parts that don’t care about color remain in the App component and are passed to the ColorPicker as JSX content, also known as the children property. When the color changes, the ColorPicker rerenders. But it still holds the same children property it got from the App last time, so React doesn’t access those subtrees. Therefore ExpensiveTree does not re-render.

export default function App() {
  return (
    <ColorPicker>
      <p>Hello, world!</p>      
      <ExpensiveTree />  
    </ColorPicker>
  );
}

function ColorPicker({ children }) {
  let [color, setColor] = useState("red");
  return (
    <div style={{ color}} >
      <input value={color} onChange={(e)= > setColor(e.target.value)} />
      {children}    
    </div>
  );
}
Copy the code