How to prevent re-rendering of components that have not changed?

Viewed 65465

I have a component that consists of several other components such as text fields, and when an input is made to the text field, all other components are re-rendered.

I would like to prevent the re-rendering and only re-render the component that actually changes.

I have seen that useCallback is the right way to do this and I have already seen how to use it. However, I'm having some trouble getting useCallBack to work correctly for my case.

Even if I set it up in a simple manner as below, each new character entered into the text field causes the button to be rendered again.

I don't see my mistake.

See working example in sandbox.

const Button = () => {
  console.log("Button Rendered!");
  window.alert("Button Rendered");
  return <button onClick="">Press me</button>;
};

export default function App() {
  const [textInput, setTextInput] = useState("Hallo");

  const onChangeInput = useCallback(
    (e) => {
      setTextInput(e.target.value);
    },
    [textInput]
  );

  return (
    <div>
      <input
        type="text"
        onChange={onChangeInput}
        value={textInput}
      />
      <Button />
    </div>
  );
}

I am happy for any calrification.

5 Answers

Personally I would avoid React.memo / React.useRef / React.useCallback.

The simplest solution to your example is just create another component, and store the state with this.

eg.

const Button = () => {
  console.log("Button Rendered!");
  window.alert("Button Rendered");
  return <button onClick="">Press me</button>;
};

const TextInput = () => {
  const [textInput, setTextInput] = useState("Hallo");
  const onChangeInput = useCallback(
    (e) => {
      setTextInput(e.target.value);
    },
    [textInput]
  );
  return (
      <input
        type="text"
        onChange={onChangeInput}
        value={textInput}
      />
  );
}


export default function App() {
  return (
    <div>
      <TextInput/>
      <Button />
    </div>
  );
}

In the above if you change Text, there is not State change in App, so Button doesn't get re-rendered, no need for useMemo etc..

You will find React works really well, the more you divide your components up, not only does it solve issues of re-render, but potentially makes it a lot easier to re-use components later.

IOW: keep state as close to the component as possible, and performance will follow.

Of course your example is simple, and in a real app you will have HOC's etc to cope with, but that's another question.. :)

useCallback does not prevent rerenders. React.memo is what prevents renders. It does a shallow comparison of the previous props with the new props, and if they're the same, it skips rendering:

const Button = React.memo(() => {
  console.log("Button Rendered!");
  window.alert("Button Rendered");
  return <button onClick="">Press me</button>;
});

The only role that useCallback plays in this is that sometimes you want to pass a function as a prop to a memoized component. For the memoization to work, props need to not change, and useCallback can help the props to not change.

changing the state causes re-render component along with all his heirs, to prevent re-render some sections, you can use useMemo to prevent unwanted re-rendering...

NOTE: useMemo has some costs... so don't overuse it (In this small example, it is not recommended at all).

in this case, if you do not need to re-rendering, you can use the useRef to save the input reference to get that value whenever you need it.

e.g:

const BlahBlah = () => {
  const inputRef = React.useRef(undefined);

  return (
    <div>
        <input ref={inputRef} />
        <button onClick={() => console.log(inputRef.current.value)}
    </div>
  );
};

I had similar problem. I wanted to avoid rendering a component which dependent on part2 of state, when only part1 has changed.

I used shouldComponentUpdate(nextProps, nextState) as described here https://reactjs.org/docs/optimizing-performance.html to avoid rendering it.

I'm using it this way:

const [state_data, set_state_data] = useState([
    true, // loading.
    {}, // vehicle.
    {} // user.
]);

This allows rendering only 1x instead of 3x and React keeps track of the changes. For some reason it won't track the change of objects, but arrays yes.

Related