Reputation: 11830
I have gone through a couple of articles on useCallback
and useMemo
on when to use and when not to use but I have mostly seen very contrived
code. I was looking at a code at my company where I noticed someone have done this:
const takePhoto = useCallback(() => {
launchCamera({ mediaType: "photo", cameraType: "front" }, onPickImage);
}, []);
const pickPhotoFromLibrary = async () => {
launchImageLibrary({ mediaType: "photo" }, onPickImage);
}
const onUploadPress = useCallback(() => {
Alert.alert(
"Upload Photo",
"From where would you like to take your photo?",
[
{ text: "Camera", onPress: () => takePhoto() },
{ text: "Library", onPress: () => pickPhotoFromLibrary() },
]
);
}, [pickPhotoFromLibrary, takePhoto]);
This is how onUploadPress is called:
<TouchableOpacity
style={styles.retakeButton}
onPress={onUploadPress}
>
Do you think this is the correct way of calling it? Based on my understanding of those articles, this looks incorrect. Can someone tell me when to use useCallback
and also maybe explain useCallback
in more human terms?
Article I read: When to useMemo and useCallback.
Upvotes: 50
Views: 55529
Reputation: 518
In simple words, useCallback
is used to save the function reference somewhere outside the component render so we could use the same reference again. That reference will be changed whenever one of the variables in the dependencies array changes.
As you know React
tries to minimize the re-rendering process by watching some variables' value changes, then it decides to re-render not depending on the old-value and new-value of those variables.
So, the basic usage of useCallback
is to hold old-value and the new-value equally.
I will try to demonstrate it more by giving some examples in situations we must use useCallback
in.
useEffect
.function Component(){
const [state, setState] = useState()
// Should use `useCallback`
function handleChange(input){
setState(...)
}
useEffect(()=>{
handleChange(...)
},[handleChange])
return ...
}
useEffect
hook, it leads to an infinite loop.function Parent(){
const [state, setState] = useState()
function handleChange(input){
setState(...)
}
return <Child onChange={handleChange} />
}
function Child({onChange}){
const [state, setState] = useState()
useEffect(()=>{
onChange(...)
},[onChange])
return "Child"
}
React Context
that holds a state and returns only the state setters functions, you need the consumer of that context
to not rerender every time the state update as it may harm the performance.const Context = React.createContext();
function ContextProvider({children}){
const [state, setState] = useState([]);
// Should use `useCallback`
const addToState = (input) => {
setState(prev => [...prev, input]);
}
// Should use `useCallback`
const removeFromState = (input) => {
setState(prev => prev.filter(elem => elem.id !== input.id));
}
// Should use `useCallback` with empty []
const getState = () => {
return state;
}
const contextValue= React.useMemo(
() => ({ addToState , removeFromState , getState}),
[addToState , removeFromState , getState]
);
// if we used `useCallback`, our contextValue will never change, and all the subscribers will not re-render
<Context.Provider value={contextValue}>
{children}
</Context.Provider>
}
Example 4: If you are subscribed to the observer, timer, document events, and need to unsubscribe when the component unmounts or for any other reason. So we need to access the same reference to unsubscribe from it.
function Component(){
// should use `useCallback`
const handler = () => {...}
useEffect(() => {
element.addEventListener(eventType, handler)
return () => element.removeEventListener(eventType, handler)
}, [eventType, element])
return ...
}
That's it, there are multiple situations you can use it too, but I hope these examples demonstrated the main idea behind useCallback
. And always remember you don't need to use it if the cost of the re-render is negligible.
Upvotes: 20
Reputation: 45883
useCallback
accepts as a first parameter a function and returns a memoized version of it (in terms of its memory location, not the computation done inside). Meaning that the returned function doesn't get recreated on a new memory reference every time the component re-renders, while a normal function inside a component does.
The returned function gets recreated on a new memory reference if one of the variables inside useCallback
's dependency array (its second parameter) changes.
Now, why would you wanna bother with this? Well, It's worth it whenever the normal behavior of a function inside a component is problematic for you.
For example, if you have that function in the dependency array of an useEffect
, or if you pass it down to a component that is memoized with memo
.
The callback of an
useEffect
gets called on the first render and every time one of the variables inside its dependency array changes. And since normally a new version of that function is created on every render, the callback might get called infinitely. SouseCallback
is used to memoize it.
A memoized component with
memo
re-renders only if itsstate
orprops
changes, not because its parent re-renders. And since normally a new version of that passed function asprops
is created, when the parent re-renders, the child component gets a new reference, hence it re-renders. SouseCallback
is used to memoize it.
To illustrate, I created the below working React application. Click on that button to trigger re-renders of the parent and watch the console. Hope it clears things up!
const MemoizedChildWithMemoizedFunctionInProps = React.memo(
({ memoizedDummyFunction }) => {
console.log("MemoizedChildWithMemoizedFunctionInProps renders");
return <div></div>;
}
);
const MemoizedChildWithNonMemoizedFunctionInProps = React.memo(
({ nonMemoizedDummyFunction }) => {
console.log("MemoizedChildWithNonMemoizedFunctionInProps renders");
return <div></div>;
}
);
const NonMemoizedChild = () => {
console.log("Non memoized child renders");
return <div></div>;
};
const Parent = () => {
const [state, setState] = React.useState(true);
const nonMemoizedFunction = () => {};
const memoizedFunction = React.useCallback(() => {}, []);
React.useEffect(() => {
console.log("useEffect callback with nonMemoizedFunction runs");
}, [nonMemoizedFunction]);
React.useEffect(() => {
console.log("useEffect callback with memoizedFunction runs");
}, [memoizedFunction]);
console.clear();
console.log("Parent renders");
return (
<div>
<button onClick={() => setState((prev) => !prev)}>Toggle state</button>
<MemoizedChildWithMemoizedFunctionInProps
memoizedFunction={memoizedFunction}
/>
<MemoizedChildWithNonMemoizedFunctionInProps
nonMemoizedFunction={nonMemoizedFunction}
/>
<NonMemoizedChild />
</div>
);
}
ReactDOM.render(
<Parent />,
document.getElementById("root")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.0/umd/react-dom.production.min.js"></script>
<div id="root"></div>
It's to know that memoizing is not free, so doing it wrong is worse. In your case, using useCallback
for onUploadPress
is a waste because a non memoized function, pickPhotoFromLibrary
, is in the dependency array. Also, it's a waste if TouchableOpacity
is not memoized with memo
, which I'm not sure it's.
As a side note, there is useMemo
, which behaves and is used like useCallback
to memoize non-function but referenced values such as objects
and arrays
for the same reasons, or to memoize any result of a heavy calculation that you don't wanna repeat between renders.
A great resource to understand React render process in depth to know when to memoize and how to do it well: React Render.
Upvotes: 55
Reputation: 49263
useCallback
tells React that this function is not changing in every render, it changes only when its dependencies change (we have to pass a dependency array. In useEffect
you have the option not to pass). It was added after useEffect
to handle useEffect
related bugs as explained in other answers.
In addition to other responses, it prevents rerendering of the components. Let's say you pass an event handler to a child component as prop
,
const ParentComponent = ()=> {
const eventHandler = ()=> { }
return <ChildComponent onClick={eventHandler}/>
}
Every time, ParentComponents
rerenders, new eventHandler
is created so you are passing a new prop to ChildComponent
. Since its prop will be changing ChildComponent
will rerender. if you wrap eventHandler
with useCallback
, child component will not re-render.
Or, if you have a click handler for a button element of a component, if you wrap the click handler with useCallback
, that button element will not rerender. If you do not, a new click handler will be created for the element. When react detects any change in jsx, it rerenders that part.
Upvotes: 0