onmyway133 / blog

🍁 What you don't know is what you haven't learned
https://onmyway133.com/
MIT License
679 stars 33 forks source link

How to use useCallback in React #966

Open onmyway133 opened 10 months ago

onmyway133 commented 10 months ago

The use of useCallback and useMemo in React hooks is an adaptation to address certain limitations inherent in the functional programming style adopted by React. In JavaScript, every entity, whether it's a function, variable, or any other type, gets created in memory when the code within a function's scope is executed. This poses a challenge for React's rendering logic, which determines the need for re-rendering based on changes in input props and context. Consider an example without useCallback:

const Component = () => {
  const [counter, setCounter] = useState(0);

  const incrementCounter = () => {
    setCounter(counter + 1);
  }

  return <div>
    Count: {counter}<br/>
    <button onClick={incrementCounter}>Increase</button>
  </div>
}

In this scenario, a new instance of incrementCounter is created every time the component renders, leading React to perceive each instance as distinct. useCallback, however, caches the initial version of a function and reuses it, provided its dependencies haven't changed.

const Component = () => {
  const [counter, setCounter] = useState(0);

  const incrementCounter = useCallback(() => {
    setCounter(counter + 1);
  }, [])

  return <div>
    Count: {counter}<br/>
    <button onClick={incrementCounter}>Increase</button>
  </div>
}

With the above code, React recognizes incrementCounter as the same function across re-renders. However, useCallback introduces a new issue: the cached function might not reflect the latest variable values. It sees the variables as they were during its initial creation. To address this, dependencies are declared in useCallback, signaling when to update the cached function. In our example, we update incrementCounter when counter changes:

const Component = () => {
  const [counter, setCounter] = useState(0);

  const incrementCounter = useCallback(() => {
    setCounter(counter + 1);
  }, [counter])

  return <div>
    Count: {counter}<br/>
    <button onClick={incrementCounter}>Increase</button>
  </div>
}

This approach ensures that the component does not re-render unnecessarily. useState, useCallback, and useMemo serve to replicate traditional class-based functionality, bringing functional programming closer to a procedural style.

useMemo functions similarly to useCallback but for variables and objects. It helps reduce unnecessary re-renders by returning cached values unless specified dependencies change.

Conclusion

Despite these benefits, the new hooks-based approach in React, especially the use of useCallback, can be somewhat counterintuitive and prone to errors, such as creating unintentional loops. This complexity was not present in the class-based React paradigm.

Originally, the class-based approach in React was more straightforward in some respects. While useCallback minimizes unnecessary re-renders, it also requires re-creating the function when dependencies change, potentially leading to more re-renders than in the class-based model.