React useCallback Explained in Detail
Learn how the React useCallback hook helps optimize performance by memoizing functions. Understand when to use it, when not to use it, and why blindly optimizing can harm performance. Includes examples for dependency tracking and preventing unnecessary re-renders.
React useCallback Explained in Detail
useCallback
is a React hook that helps React remember a function between re-renders. Without it, React recreates a new function every time a component renders, which can cause unnecessary re-renders or performance issues.
Why Do We Need `useCallback`?
Every time a component re-renders, all functions inside it are recreated. Even if the code is the same, the function reference changes. This causes two main problems:
- Child components may re-render unnecessarily when passed a new function prop.
- Expensive functions get recreated and run too often.
Basic Example
import React, { useState, useCallback } from 'react';
function Counter() {
const [count, setCount] = useState(0);
const increment = useCallback(() => {
setCount(c => c + 1);
}, []);
return (
<div>
<p>Count: {count}</p>
<button onClick={increment}>Increment</button>
</div>
);
}
With Dependencies
If your function depends on props or state, add them to the dependency array. This way, React only recreates the function when those dependencies change.
function Greeting({ name }) {
const [greetCount, setGreetCount] = useState(0);
const greet = useCallback(() => {
console.log(`Hello, ${name}`);
setGreetCount(c => c + 1);
}, [name]);
return (
<div>
<p>Greetings sent: {greetCount}</p>
<button onClick={greet}>Say Hello</button>
</div>
);
}
With Expensive Functions and React.memo
When you pass a function to a memoized child component, React will avoid re-rendering the child unless the function actually changes. This is where useCallback
is very useful.
import React, { useState, useCallback } from 'react';
function ExpensiveChild({ compute }) {
console.log('Child re-rendered');
return <div>Result: {compute()}</div>;
}
const MemoizedChild = React.memo(ExpensiveChild);
function App() {
const [value, setValue] = useState(0);
const expensiveFunction = useCallback(() => {
console.log('Running expensive calculation...');
return value * 1000;
}, [value]);
return (
<div>
<button onClick={() => setValue(v => v + 1)}>Increase</button>
<MemoizedChild compute={expensiveFunction} />
</div>
);
}
When to Use `useCallback`?
- When passing a function to a memoized child (
React.memo
). - When your function does heavy calculations.
- When you notice unnecessary re-renders.
When Not to Use
- For small, simple functions.
- If there are no performance problems.
- Just for the sake of using it (blind optimization).
Summary
useCallback
memoizes a function so that it only changes when dependencies change. It helps prevent unnecessary re-renders and should be used wisely for performance optimization.