UseCallback, same as useMemo hook, is useful in situation, when you want to improve performance of your app. Thanks to it, you can avoid useless renders, so app can works faster.
UseCallback returns memorized version of the callback function, that only changes if one of the dependencies has changed. This means, that if you have a parent component with children, they are rendering every time when parent is rendering. But you avoid children rendering if you pass to it function wrapped in useCallback hook, and at the same time wrap child component in React.memo higher-order component. Sounds confusing? Lets see it on example:
App.je component contains our parent component, named HookExample:
import React from 'react';
import HookExample from './components/hookExample.js';
function App() {
return (
<HookExample />
);
}
export default App;
So far, it’s easy. Now lets see what contains parent component:
const HookExample = () => {
const [counter, setCounter] = useState(0);
const changeCounter = (sign) => {
if (sign === "+") {
setCounter(c => c + 1);
}
else {
setCounter(c => c - 1);
}
}
console.log("Parent",);
return (
<div className="App">
<div>
<p>Counter: {counter} </p>
<Child2 changeCounter={changeCounter}/>
</div>
</div>
);
}
export default HookExample;
Parent component contains counter state and changeCounter function, that changes counter value. It returns counter value and child, to which pass changeCounter function.
Child component looks like this:
import React from 'react';
const Child = ({changeCounter}) => {
console.log("Child");
return (
<div style={{backgroundColor: "blue", color: "white"}}>
<p>Child:</p>
<div>
<button onClick={() => changeCounter("-") }>-1</button>
<button onClick={() => changeCounter("+") }>+1</button>
</div>
</div>
)
};
export default Child;
Now, each time when you click on +1 or -1 button you’ll see, that in console are logged two lines: one from parent component and one from child component. Imagine now, that you have 100 children in your parent component. Each of them is rendered, so each on them will log one line, but albo will make other calculations that you put in it. This takes some time and resources, and is unnecessary if any data passed to children are not change. So, as I mentioned earlier, you can avoid children rendering, and saving some time and resources by passing to them function wrapped in useCallback hook:
const changeCounter = useCallback(
(sign) => {
if (sign === "+") {
setCounter(c => c + 1);
}
else {
setCounter(c => c - 1);
}
}, [setCounter]
)
Remember also about wrapping child component in React.memo higher-order component:
const Child = React.memo(({changeCounter}) => {
console.log("Child");
return (
<div style={{backgroundColor: "blue", color: "white"}}>
<p>Child 2:</p>
<div>
<button onClick={() => changeCounter("-") }>-1</button>
<button onClick={() => changeCounter("+") }>+1</button>
</div>
</div>
)
});
Now if you click +1 or -1 button you’ll see, that only parent component logged its sentence to console, and child component don’t. UseCallback passed memorized version of function to child, and if it’s dependencies (in this case setCounter function) do not change, then child component will always receive the same props. Because you wrapped child component in React.memo higher-order component, which works similar to React.PureComponent, but is used only in functional components, when child component receive the same props as previous, React will skip rendering the component, and reuse the last rendered result.
According to documentation, “This method only exists as a performance optimization. Do not rely on it to “prevent” a render, as this can lead to bugs”.
Click on DEMO to see how useCallback hook works
You can find this code in repo: https://github.com/love-coding-pl/React-Hooks-Examples/tree/Hook-7-useCallback