How to Use React useCallback with a Parameter to Optimize Performance
Use useCallback to memoize callback functions that accept parameters by defining arguments in the function signature while supplying values at invocation time, keeping the function reference stable across renders when dependencies remain unchanged.
The useCallback Hook is essential for performance optimization in React applications, particularly when passing functions to memoized child components. According to the facebook/react source code, this Hook leverages the reconciler's internal memoization mechanism to preserve function references. Understanding how to effectively use react usecallback with a parameter allows developers to maintain referential stability while handling dynamic data.
How useCallback Works in React's Source
Hook Dispatcher Entry Point
In packages/react/src/ReactHooks.js, the useCallback Hook serves as the public API entry point that forwards to the current renderer's implementation:
export function useCallback<T>(callback: T, deps: Array<mixed> | void | null): T {
return dispatcher.useCallback(callback, deps);
}
Reconciler Memoization Logic
The actual memoization occurs in packages/react-reconciler/src/ReactFiberHooks.js. During the initial render, mountCallback stores the callback alongside its dependency array in a Hook node. Subsequent renders invoke updateCallback, which compares the new dependencies against the previous list using areHookInputsEqual. If the arrays are shallowly equal, React returns the previously stored function reference unchanged; otherwise, it creates a new memoized wrapper.
Implementing useCallback with Parameters
The Parameter Pattern
When using react usecallback with a parameter, define the parameter in the callback's signature at definition time. The arguments are supplied later when the component or event handler invokes the function:
const handleClick = React.useCallback(
(id: number) => {
onSelect(id);
},
[onSelect]
);
The callback reference (handleClick) stays stable across renders as long as onSelect does not change, while the id parameter is supplied at call-time.
Referential Stability Benefits
This pattern ensures the function reference remains identical across renders when dependencies are unchanged. Child components receiving this callback as a prop avoid unnecessary re-renders because their reference equality checks remain stable. Additionally, any useEffect that lists the callback in its dependency array will not re-run due to reference changes.
Practical Code Examples
Counter with Dynamic Increments
This example demonstrates a stable incrementer that receives variable amounts:
import React from 'react';
function Counter() {
const [count, setCount] = React.useState(0);
const increment = React.useCallback((delta: number) => {
setCount(prev => prev + delta);
}, []);
return (
<>
<p>Count: {count}</p>
<button onClick={() => increment(1)}>+1</button>
<button onClick={() => increment(5)}>+5</button>
</>
);
}
The increment function is created once during the initial render. Clicking different buttons merely invokes the same stable reference with different arguments.
Optimized List Rendering
When passing callbacks to memoized children, parameterization prevents unnecessary re-renders:
const List = React.memo(function List({items, onSelect}) {
return (
<ul>
{items.map(item => (
<ListItem key={item.id} item={item} onSelect={onSelect} />
))}
</ul>
);
});
function Parent() {
const [selected, setSelected] = React.useState(null);
const items = React.useMemo(() => generateItems(), []);
const handleSelect = React.useCallback(id => {
setSelected(id);
}, []);
return (
<>
<List items={items} onSelect={handleSelect} />
<p>Selected ID: {selected}</p>
</>
);
}
List only re-renders when items or handleSelect change. Because handleSelect is memoized, child ListItem components receive a stable prop reference and avoid unnecessary re-renders even when the parent updates for other reasons.
Stable Callbacks with Mutable Refs
Combine useCallback with useRef to read mutable values without breaking referential stability:
function SearchBox({onSearch}) {
const queryRef = React.useRef('');
const submit = React.useCallback(() => {
onSearch(queryRef.current);
}, [onSearch]);
return (
<form onSubmit={e => { e.preventDefault(); submit(); }}>
<input
type="text"
onChange={e => (queryRef.current = e.target.value)}
/>
<button type="submit">Search</button>
</form>
);
}
The submit function remains stable across renders, while the mutable query is stored in a ref. This avoids creating a new function for each keystroke while still accessing current input values.
Best Practices for useCallback Optimization
- Include all dependencies: List every value referenced inside the callback in the dependency array to prevent stale closures.
- Target memoized children: Apply
useCallbackspecifically when passing functions to components wrapped inReact.memoor usingshouldComponentUpdate. - Avoid overuse: Do not wrap every callback; unnecessary memoization adds overhead without benefit when functions aren't props or effect dependencies.
- Pair with useMemo: Combine with
useMemowhen deriving complex objects that contain callbacks. - Use stable references: Prefer refs or stable state selectors over mutable objects in dependency arrays.
Summary
- React's
useCallbackHook stores function references inReactFiberHooks.jsand returns the cached version when dependencies match according toareHookInputsEqual. - Parameters are defined in the callback signature but supplied at invocation time, allowing a single stable reference to handle multiple argument values.
- This optimization prevents child component re-renders and unnecessary effect executions in the facebook/react reconciler.
- The Hook is implemented in
packages/react/src/ReactHooks.jswith core memoization logic residing inpackages/react-reconciler/src/ReactFiberHooks.js.
Frequently Asked Questions
Can useCallback memoized functions accept dynamic parameters?
Yes. The memoized function returned by useCallback can accept parameters just like any standard function. Define the parameters in the function signature when creating the callback, then pass arguments when invoking it. React preserves the function reference while allowing different arguments at each call site, making react usecallback with a parameter effective for handling dynamic data without breaking referential equality.
Does useCallback prevent re-creation when parameters change?
No. useCallback controls when the function reference changes based on the dependency array, not the arguments passed at invocation. The parameters supplied during the call do not affect memoization; only the dependencies listed in the second argument determine whether React returns a new function reference from mountCallback or updateCallback.
When should I avoid using useCallback with parameters?
Avoid using this Hook for callbacks that are not passed as props to child components or included in dependency arrays of effects. The facebook/react source code shows that the reconciler's mountCallback and updateCallback functions add overhead to the render phase. If the callback is only used locally and never affects other components' render cycles, the optimization provides no benefit and adds unnecessary indirection.
How does React compare dependencies in useCallback?
According to ReactFiberHooks.js, React uses areHookInputsEqual to perform shallow comparison of array elements. It checks each item in the new dependency array against the corresponding item in the previous array using strict equality (===). If any item differs, React creates a new callback reference; otherwise, it returns the cached function from the fiber's Hook node.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →