Hooks have revolutionized how developers write React JS applications! With React useCallback, you can optimize your components by memoizing functions. Simply remember the result of a heavily processed function and use it until the dependencies are updated.
Feel free to use code examples!
Learn how to prevent unnecessary re-renders and Boost your app performance!
Understanding the Basics of React Hooks
React hooks are awesome! They are easy and simple — I love to use them.
Hooks let functional React components use state, and other React features.
For example, I can extract state and logic into a custom hook and reuse it across different components — a great way to share components and reduce code duplication!
Before hooks, when React only started gaining popularity, functional components had less control than class components.
However, as time passed, developers wanted more powerful features to work with in a functional style. Finally, anticipated hooks were released in React 16.8 in Feb 2019.
React hooks initially caused some confusion because they represent a new programming paradigm in the React library.
As developers moved from class components (with lifetime methods) to functional components (with hooks), they faced a learning curve in understanding the new flow.
I was not an exception, even though I was already a “functional style” fan, lol!
However, once the other developers and I got the concepts behind, hooks simplified the code and made it more readable.
Hooks encouraged me to write a more functional programming style, using pure functions and avoiding stateful logic.
The two most essential hooks are useState and useEffect — the first I learned. These two replace the need for class components and lifecycle methods! After using these hooks, I realized how much simpler my code became.
Some other commonly used hooks are useCallback, useMemo, useContext, and useReducer.
All hooks provide a way to use React features without writing class components – hurray!
What is the React useCallback Hook?
useCallback
returns a memoized version of a function. Think of the memoized version as a copy of a function code that Reacts saves and returns for you on each rendering.
With a memorized function, we can prevent React from re-creating the function on every render.
In a regular case, Javascript will create a new instance of the handleClick
function every time a user clicks the Increment button.
function Parent() { const [count, setCount] = useState<number>(0) const handleClick = () => { console.log('Click!') setCount(count + 1) } return ( <> <p>{count}</p> <button onClick={handleClick}>Increment</button> </> ) }
Whenever you click the Increment
button, the handler function changes state, forcing React to re-render the count number. On re-render, Javascript initializes a new instance of the handleClick
function and assigns it to the MyChild
click.
Everything seems legit here. But what if you don’t want JavaScript to reinitialize the handler function and use the one already used before rendering?
To ensure that JavaScript will not reinitialize the handleClick
function on every render, you can use the useCallback.
Memoizing the child React component is a good use case to keep the same function instance.
What Is Memoization?
Imagine you have a BigChild
component that fetches data (or asynchronously retrieves it from any store) and renders it as a list of items. The Parent
of this component passes a handler
function that the BigChild
component is assigned on every list item click. This is a very common situation, I can tell.
But, during the implementation, it turned out that the amount of data you need to render is colossal, and data retrieval takes 3 seconds every time…
import { memo, useEffect, useState } from 'react'; type ItemType = { id: number; }; function BigChild({ handler }: { handler: (index: number) => void }) { const [list, setList] = useState<ItemType[]>([]); useEffect(() => { const controller = new AbortController(); // Immitates 3 seconds API call or any other async data retriving // setTimeout updates list state in 3000 ms const interval = setTimeout(() => { console.log('Data fetched!'); setList(items); }, 3000); return () => { controller.abort(); clearTimeout(interval); }; }, []); return ( <> {list.map(item => ( <div onClick={() => handler(i)} key={item.id}>{item.id}</div> ))} </> ); }
3 seconds are too long — your clients can’t wait for that!
You must do anything to prevent this component from rendering. This is where memoization comes into play.
Memoization is a technique that stores the result of function calls and returns the cached result when the call occurs again.
Simply import a memo
function and wrap the component to start using memorization.
import { memo } from 'react'; function BigChild({ handleClick }) { // ... BigChild implementation } export default memo(BigChild);
However, In the case of BigChild
, memoization will fail because the code will differ due to a new handleClick
function.
Memoization works only when there is an existing result for existing code in the memory.
If any part of the memoized function differed, such as its implementation or dependencies, the memoization will fail, and React will recalculate it.
Memoization relies on the function’s identity to determine whether to return a cached result or recalculate a new function. If the function’s identity changes, the cached result is no longer valid, and React must recalculate the function.
To achieve the desired memoization for the BigChild
component, we need to use both memo
and useCallback
.
memo
function will memoize the biggest part of the component while useCallback
memoizing the handleClick
function.
Keep reading to see the result, as we need to discuss some other aspects before the final implementation
How Does The useCallback Hook Can Help?
useCallback prevents unnecessary re-renders of components.
You typically want to minimize the re-rendering time to deliver the best UX. Especially when dealing with complex logic or frequent updates!
Wrap a function in useCallback — React will memoize the function instance and return it as long as the dependencies array remains the same.
If you deal with scenarios where callback functions are computationally expensive, useCallback significantly impacts performance.
Benefits of Using useCallback Hook
useCallback helps optimize your React app performance, stability, and readability. These are some main detailed advantages:
- Performance Optimization: improves performance in components with complex logic or frequent updates. Useful when passing callbacks as props to child components.
- Preventing Component Re-renders: No unnecessary re-renders unless the dependencies change!
- Stable References: Use any function safely in useEffect! useCallback ensures stable references, preventing stale closure issues.
- Improved Code Readability: The Dependencies array clarifies which values the function relies on.
- Optimizing Hooks: Memoize hook functions so React can memorize consuming components.
- Managing Large Applications: Improved performance in large applications with complex codebases.
I always use useCallback to create more efficient applications.
This is especially beneficial in areas where performance optimization is crucial for a smooth user experience.
What is the useMemo Hook in React?
The useMemo
in React memoizes the result of a function. It takes a function and an array of dependencies and returns a memoized value.
React will recalculate the memoized value only when dependencies or the code inside the function has changed.
In this example, React will memoize the result of the function passed to useMemo based on the a
and b values
. If a
or b
changes, React re-executes the function, and memoizes the new result.
The difference between useCallback and useMemo
The useCallback
and useMemo
hooks in React are similar — both memoize values to enhance UX.
However, they are used for different purposes and have some key differences:
useCallback | useMemo | |
---|---|---|
Purpose | Memoizes function's code. | Memoizes the result of a function. |
Usage | Optimizes functions passed as dependencies. | Optimizes expensive or complex calculations. |
Return Value | A memoized version of the function. | A memoized value. |
Dependencies | Array of dependencies; the memoized value is only recalculated if one of the dependencies changes. |
useCallback
memoizes functions, while useMemo
memoizes values.
Both hooks boost performance and prevent unnecessary recalculations or recreations of functions/values.
Practical Implementation and Examples
The goal is to prevent unnecessary rerenders for a component with heavily loading data. This is a very common situation — you want to know how to handle it effectively. Find the code source here!
We already have some code written in this article. At this moment, we have two components, Parent and BigChild.
This is the Parent
component – it initializes a clickHandler
function and passes it to the BigChild
component. The Increment
button triggers the count
state update and renders the component.
import BigChild from './BigChild' function Parent() { const [count, setCount] = useState<number>(0) const handleClick = (index: number) => { console.log(`Click on BigChild Item #-${index}`) } return ( <> <BigChild handler={handleClick} /> <p>{count}</p> <button onClick={() => setCount(count + 1)}>Increment</button> </> ); }
The BigChild
renders a list of data it gets in 3 seconds after initial rerender. Also, BigChild
destructs the handler
callback and assigns it to items’ clicks.
import { memo, useEffect, useState } from 'react'; // Fake data const items = [{ id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }, { id: 5 }]; type ItemType = { id: number; }; function BigChild({ handler }: { handler: (index: number) => void }) { console.log('BigChild Rerenders'); const [list, setList] = useState<ItemType[]>([]); useEffect(() => { // Immitates API call or any other async data retriving // setTimeout updates list state in 1000 ms const timer = setTimeout(() => { setList(items); }, 1000); return () => clearTimeout(timer); }, []); return ( <div> {list.map((item, i) => ( <div onClick={() => handler(i)} key={item.id}>{item.id}</div> ))} </div> ); } export default memo(BigChild);
The BigChild
component is memoized using the React.memo
, but it still rerenders on every Parent
component rerender. You will see the “BigChild Rerenders” string in the console from line 11 every time you click the Increment Button on the Parent component. It causes unnecessary rerenders.
This happens because the Parent
passes a new handler function as a prop to BigChild
on every rerender.
React ignores the memoized version of BigChild
and rerenders it unnecessarily since the reference to the handler function changes on every render
To fix this issue, use the useCallback
hook in the Parent
component and memoize the handleClick
function.
How To Implement useCallback In React Application?
Import the useCallback
from the React library and wrap a handleClick function with it.
Don’t forget to pass an empty array []
as the second argument to useCallback
! This tells React always to remember the function and use the same instance for every rerender.
import { useState, useCallback } from 'react'; import BigChild from './BigChild'; function Parent() { const [count, setCount] = useState<number>(0); const handleClick = useCallback((index: number) => { console.log(`Click on BigChild Item #-${index}`); }, []); return ( <> <BigChild handler={handleClick} /> <p>{count}</p> <button onClick={() => setCount(count + 1)}>Increment</button> </> ); }
Now, React.memo
will finally memoize the BigChild
component as its props aren’t changed on the Parent rerender — perfect!
What Are Some Real-world Scenarios Where useCallback Can Be Beneficial?
In real-world projects, I’ve seen useCallback usage mostly for the following cases:
Optimizing Child Components:
- When a parent component renders a child component, useCallback prevents unnecessary re-renders of the child component. When the Parent changes — the individual components don’t need to be recreated.
Optimizing Dependencies in useEffect:
- In the useEffect hook, use useCallback to memoize functions used as dependencies. This ensures the effect only runs when necessary!
Optimizing Virtualized Lists:
- Use useCallback when rendering large lists with libraries like react-window or react-virtualized. Prevent unnecessary re-renders of list items that are not currently in view.
Overall, useCallback
is useful where you want to optimize the performance of components and improve the UX.
Conclusion
The useCallback hook in React is a powerful tool! It boosts performance and improves the user experience in various scenarios.
In my experience, I’ve found useCallback useful when working with complex UI that involves heavy data manipulation.
This hook can prevent unnecessary re-renders and helps understand the nuances of React’s rendering cycle.
I highly recommend mastering the use of the Callback hook for anyone serious about React development!
Thank you for reading this article — please, let me know if you have any suggestions! And keep learning!!
Welcome to the programming blog. Here, you’ll find insights, tutorials, and helpful resources.