Synergizing Optimizations with useCallback() useMemo() and React’s Re-Render

How we decide which values and functions’ references need to be updated while re-rendering a React component.

Vikas Sharma
Frontend Weekly

--

React Coding
Photo by Lautaro Andreani on Unsplash

In React, functional components can re-render due to various factors, such as changes in state, props, or context. When a functional component re-renders, the code within the component runs again, and this can result in the creation of new function instances if you define functions directly within the component body or within event handlers.

To prevent unnecessary function recreations and optimise performance, we use useCallback() and useMemo() hooks to memoize functions and values that should remain consistent across renders, even if other parts of the component change. This can be particularly useful when passing functions as props to child components or using them within useEffect() or event handlers to ensure stable behaviour between renders.

Let’s understand re-rendering first with the help of a code

const displayCountries = () => {
setcountriesToDisplay(
window.innerWidth > 640
? languages.slice(
// conditional code
)
: languages.slice(
// conditional code
)
);
};

Suppose we have an arrow function displayCountries() that is used to set the state of countries to be displayed according to the user’s window size.

During re-rendering of our component due to changes in state, props, or context containing the code might project following behaviors :

  1. Function Recreated on Every Render : In this case, displayCountries() is redefined on every render of the component where it's used. This means that the function reference changes with each render.
  2. Effect Dependency Considerations : If you use displayCountries as a dependency in a useEffect() elsewhere in your component, it can potentially lead to unnecessary re-executions of the effect when displayCountries is recreated, even if the dependencies of the effect haven't changed. This can impact the performance of your component.
useEffect(() => {
// Effect logic
}, [displayCountries]);

In this code, displayCountries is the dependency of the useEffect(). If displayCountries is recreated on every render, it means the function reference changes with each render, regardless of whether (the actual dependencies) of effect have changed. This can result in the effect running more frequently than necessary, impacting the performance of your component.

3. Impact on Child Components : If you pass displayCountries as a prop to child components or use it within other hooks or functions, it can also lead to those components or functions being re-rendered or re-executed more frequently than necessary.

function ChildComponent({ onClick }) {
// ...
return <button onClick={onClick}>Click me</button>;
}

And using it in the parent component:

<ChildComponent onClick={displayCountries} />

If displayCountries is recreated on every render of the parent component, it means that every time the parent component re-renders, it passes a new function reference to ChildComponent. As a result, ChildComponent might re-render even when its props haven't changed, leading to potential inefficiencies in your application.

To address Function Recreation, Effect Dependency and Impact on Child Components useCallback() and useMemo() come into place.

useCallback() and useMemo() are both React hooks that help optimise performance by memoizing values, but they serve different purposes and are used in different situations. Here are the key differences between them and how to decide which one to use.

useCallback():

  1. Purpose: useCallback() is primarily used to memoize functions, ensuring that the function reference remains stable unless its dependencies change. It's often used for event handlers and functions passed as props to child components to prevent unnecessary re-renders.
  2. Use Cases: When you want to memoize a function to prevent re-creation on every render also if you pass functions as dependencies to other hooks (e.g., useEffect), and you want to avoid causing those hooks to re-run unnecessarily.
  3. Example:
const memoizedFunction = useCallback(() => {
// Function logic
}, [dependency1, dependency2]);

The dependencies array is an array of values that, when changed, will trigger the recreation of the memoized function. If the dependencies array is empty, the function will be memoized and won't change during re-renders.

useMemo():

  1. Purpose: useMemo() is used to memoize the result of a computation or expression based on certain dependencies. It's used to optimise the calculation of values that don't need to be recalculated on every render.
  2. Use Cases: When you need to memoize a computed value, such as a derived data value or a formatted representation of data. When you want to prevent the re computation of a value that depends on certain inputs (e.g., props or state) and avoid unnecessary work.
  3. Example:
const memoizedValue = useMemo(() => {
// Computation or transformation based on dependencies
return computeValue(dependency1, dependency2);
}, [dependency1, dependency2]);
 const columns = useMemo(() => [
{
field: "module_name", type: "string", headerName: "Module Name",
valueGetter: ({ row }) => {
return row.module_name;
}
},
{
// other columns
}
], [])

How to Decide Which to Use:

  1. Function vs. Value: Decide whether you need to memoize a function or a value. If you’re working with functions (e.g., event handlers, functions passed as props), use useCallback(). If you're working with computed values or derived data, use useMemo().
  2. Stable References: Use useCallback() when you want to ensure that a function's reference remains stable between renders, especially when passing functions as dependencies to other hooks or components.
  3. Expensive Computations: Use useMemo() when you have expensive computations or transformations of data that don't need to be recalculated on every render. Memoize the result to improve performance.
  4. Consider Dependencies: Both hooks accept a dependency array, so consider which dependencies your function or computation relies on. Use useCallback() or useMemo() when those dependencies change and you want to recalculate or recreate the memoized value or function.
  5. Performance Profiling: Use performance profiling tools like the React DevTools Profiler to identify components that may benefit from memoization. Measure the impact of your optimizations on performance.

useCallback() for Event Handlers and Effects, Passing Functions as Props, Stable References useMemo() for Expensive Computations, Derived Data, Preventing Unnecessary Recomputations, Expensive API Calls results.

Conclusion

The primary benefit of using useCallback() and useMemo() is that they memoize values and functions, ensuring that their references remain stable between re-renders. This can be especially useful when passing event handlers to child components or using them in useEffect() dependencies, as it prevents unnecessary re-subscriptions or re-executions. It lead to memory efficiency because it reduces the creation of unnecessary object instances, especially in components with frequent re-renders.

If you read so far, you might not wanna miss on my another interesting article. Check it out :))

https://medium.com/stackademic/overlapping-executions-with-setinterval-in-js-2583a905b22c

--

--

Vikas Sharma
Frontend Weekly

Software Engineer | Frontend | UI/UX | Customer Experience