I can't believe I misused useMemo and useCallback for so long?

June 6, 2023 1269hotness 0likes 0comments

We know that useMemo and useCallback are mainly used to cache intermediate states and reduce meaningless render to improve performance. But recently I have found that I have been misunderstanding their use!

misunderstanding of useMemo

Please take a look at the following code. Even with useMemo , the second subcomponent is re-rendered without the change of isZero !

import { useCallback, useMemo, useState } from "react";

const Child = ({ value, onClick }) => {
  return (
    <div
      style={{
        height: 100,
        background: `#${(~~(Math.random() * (1 << 24))).toString(16)}`
      }}
    >
      my value is {value.toString()}
    </div>
  );
};

export default function App() {

  const [count, setCount] = useState(0);

  const isZero = useMemo(() => !!(count % 3), [count]);

  const onClick = useCallback(() => setCount(count + 1), [count]);

  return (
    <div className="App">
      <button onClick={onClick}>click me</button>
      <Child value={count} />
      <Child value={isZero} />
    </div>
  );
}

💡 related reading

in fact, the reason has also been mentioned in previous articles:

React every time the state of a component changes, it starts from the current component until all leaf node components are re-rendered.

The

article also mentions the solution to this problem: the subcomponent is wrapped with the memo function, and the component can render as expected.

however, at this point we remove useMemo , and the subcomponents are still rendered as expected.

memo is similar to useMemo , which is based on a shallow comparison of Object.is and is only valid for non-reference types.

so in the above example, it makes no sense to use useMemo .

misunderstanding of useCallback

however, in the above example, the component renders as expected even if the onClick function does not use useCallback . This is because the App component is destined to be rendered regardless of whether the cache of the callback function of onClick has changed.

so, now we have a reasonable code, as follows:

import { memo, useCallback, useMemo, useState } from "react";

const Child = memo(({ value, onClick }) => {
  return (
    <div
      style={{
        height: 100,
        background: `#${(~~(Math.random() * (1 << 24))).toString(16)}`
      }}
    >
      my value is {value.toString()}
    </div>
  );
});

export default function App() {

  const [count, setCount] = useState(0);

  // const isZero = useMemo(() => !!(count % 3), [count]);
  const isZero = !!(count % 3);

  // const onClick = useCallback(() => setCount(count + 1), [count]);
  const onClick = () => setCount(count + 1);

  return (
    <div className="App">
      <button onClick={onClick}>click me</button>
      <Child value={count} />
      <Child value={isZero} />
    </div>
  );
}

when on earth should I use useCallback ?

take a look at the example below. Add the following code to the above code:

  const onClickMethod = () => console.log("lll");

  return (
    <div className="App">
      <button onClick={onClick}>click me</button>
      <Child value={count} onClick={onClickMethod} />
      <Child value={isZero} onClick={onClickMethod} />
    </div>
  );

at this point, it is found that the component cannot render as expected, and the second Child component will be re-rendered regardless of whether the Child has changed.

this is because the onClickMethod method at this time is taken as the onClick property of the Child component.

if you now wrap the onClickMethod method with useCallback , it will be normal again.

const onClickMethod = useCallback(() => console.log("lll"), []);

this is the correct use of useCallback !

Summary

when we write components, we should follow the following rules, which can effectively improve page performance:

  • wrap components (reduce rendering times) with memo methods as much as possible

  • use useMemo (reduce rendering times)

    when the attribute of a child component is in an intermediate state of a non-reference type.

  • use useCallback (reduce rendering times)

    when the property of the subcomponent is a function

  • only works on attributes within the scope of the current component. Try not to use useMemo and useCallback (reduce calls)

well, that's all for today's sharing. I hope you don't misuse useMemo and useCallback as I do!

InterServer Web Hosting and VPS

Aaron

Hello, my name is Aaron and I am a freelance front-end developer

Comments