useCallback() often is used in conjunction with useEffect() because it allows you to prevent the re-creation of a function. For this, it’s important to understand that functions are just objects in JavaScript.
Therefore, if you have a function (A) inside of a function (B), the inner function (=A) will be recreated (i.e. a brand-new object is created) whenever the outer function (B) runs.
That means that in a functional component, any function you define inside of it is re-created whenever the component rebuilds
Example:
const MyComponent = props => {
const innerFunction = () => {
// a function in a function!
// this function object (stored in the 'innerFunction' constant) is constantly re-built
// to be precise: It's re-built when MyComponent is re-built
// MyComponent is re-built whenever its 'props' or 'state' changes
};
};
Typically, it’s not a problem, that innerFunction is re-created for every render cycle.
But it becomes a problem if innerFunction is a dependency of useEffect():
const MyComponent = props => {
const innerFunction = () => {
// do something!
};
useEffect(() => {
innerFunction();
// The effect calls innerFunction, hence it should declare it as a dependency
// Otherwise, if something about innerFunction changes (e.g. the data it uses), the effect would run the outdated version of innerFunction
}, [innerFunction]);
};
Why is the above code problematic?
The effect re-runs whenever innerFunction changes. As stated, it is re-created whenever MyComponent re-builds
Because functions are objects and objects are reference types, that means that the effect will re-run for every render cycle
That might still not be a huge problem, but it is, if innerFunction does something that causes MyComponent to re-build (i.e. if it either does something that changes the props or the state).
Now, you would have an infinite loop!
useCallback() helps you prevent this.
const MyComponent = props => {
const innerFunction = useCallback(() => {
// do something!
});
useEffect(() => {
innerFunction();
// The effect calls innerFunction, hence it should declare it as a dependency
// Otherwise, if something about innerFunction changes (e.g. the data it uses), the effect would run the outdated version of innerFunction
}, [innerFunction]);
};
By wrapping it around a function declaration and defining the dependencies of the function, it ensures that the function is only re-created if its dependencies changed.
Hence the function is NOT re-built on every render cycle anymore => You break out of the infinite loop!
On every render, everything that’s inside a functional component will run again. If a child component has a dependency on a function from the parent component, the child will re-render every time the parent re-renders even if that function “doesn’t change” (the reference changes, but what the function does won’t).
It’s used for optimization by avoiding unnecessary renders from the child, making the function change the reference only when dependencies change. You should use it when a function is a dependency of a side effect e.g. useEffect.
function ExampleChild({ callbackFunction }) {
const [value, setValue] = React.useState(0);
React.useEffect(() => {
setValue(value + 1)
}, [callbackFunction]);
return (<p>Child: {value}</p>);
}
function ExampleParent() {
const [count, setCount] = React.useState(0);
const [another, setAnother] = React.useState(0);
const countCallback = React.useCallback(() => {
return count;
}, [count]);
return (
<div>
<ExampleChild callbackFunction={countCallback} />
<button onClick={() => setCount(count + 1)}>
Change callback
</button>
<button onClick={() => setAnother(another + 1)}>
Do not change callback
</button>
</div>
)
}
ReactDOM.render(<ExampleParent />, document.getElementById('root'));
useCallback:
Whenever you have a function that is depending on certain states. This hook is for performance optimization and prevents a function inside your component to be reassigned unless the depending state is changed.
const myFunction = useCallback(() => {
// execute your logic for myFunction
}, [state]);
Without useCallback, myFunction will be reassigned on every render. Therefore it uses more compute time as it would with useCallback.
let input = 123;
const output = useCallback(() => {
return input + 1;
}, [
input,
]);
// The above function has not run yet but we can run it now.
console.log( output() ); // 124
input = 125; // changes the result as the function is running again
console.log( output() ); // 126
useCallback
- Got functions that you don’t want to be re-created on every component render?
- Want a function that isn’t called on component mount or unmount?
Use useCallback
const [val, updateValue] = useState(0);
const Compo = () => {
/* inc and dec will be re-created on every component render.
Not desirable a function does very intensive work.
*/
const inc = () => updateValue(x => x + 1);
const dec = () => updateValue(x => x - 1);
return render() {
<Comp1 onClick={inc} />
<Comp2 onClick={dec} />
}
}
useCallback to the rescue
const [val, updateValue] = useState(0);
const Compo = () => {
const callbackInc = useCallback(() => {
setCount(currentVal => currentVal + 1);
}, []);
const callbackDec = useCallback(() => {
setCount(currentVal => currentVal - 1);
}, []);
return render() {
<Comp1 onClick={callbackInc} />
<Comp2 onClick={callbackDec} />
}
}
If the argument passed to setCount isn’t a function, then the variables you would want useCallback to ‘watch’ out for must be specified in the dependencies array less there will be no change effect.
const callbackInc = useCallback(() => {
setCount(val + 1); // val is an 'outside' variable therefore must be specified as a dependency
}, [val]);