8

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 have 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 from those articles, this looks in-correct. Can someone tell me when to use useCallback and also maybe explain useCallback in more human terms?

Article I read: https://kentcdodds.com/blog/usememo-and-usecallback.

Mark Rotteveel
  • 90,369
  • 161
  • 124
  • 175
anny123
  • 5,060
  • 11
  • 50
  • 95
  • Yes, this is definitely wrong, since the dependencies `pickPhotoFromLibrary` and `takePhoto` change on every render; which means that `onUploadPress` also changes on every render and it works just as if you had not used `useCallback` at all. – Bergi Feb 25 '22 at 12:00
  • Does this answer your question? [Should I wrap every prop with useCallback or useMemo, when to use this hooks?](https://stackoverflow.com/questions/55310682/should-i-wrap-every-prop-with-usecallback-or-usememo-when-to-use-this-hooks) – Samson Mar 15 '22 at 17:04

2 Answers2

3

1. Overview

useCallback returns a normal JavaScript function, regarding how to use it. It is the same function as the one it gets as first parameter regarding what it does. Though the returned function is memoized. Which means when your component re-renders, React will not recreate that function (which is the behaviour for a normal function inside a component).

React will recreate a new version of that function if one of the variables inside the array (the second parameter of useCallback) changes. It's worth it if you have that function in the dependencies array of an useEffect for example. It's also worth it if you pass it down to a component that is memoized with React.memo.

The callback of an useEffect gets called on the first render and every time one of the variables inside the dependencies array changes. And since normally a new version of that function is created every time the component re-renders, the callback might gets called infinitely. That is why useCallback is used to memoize that function.

A memoized component will re-render only if its state or props changes, not because its parent re-renders. And because normally a new version of that passed function is created when the parent re-renders, the child component gets a new reference of that function, so it re-renders. That is why useCallback is used to memoize the passed function.

2. Important note

Memoizing is not free. Bad memoizing is worse than no memoizing at all. Here is the most complete and shortest ressource I have ever seen for understanding in depth, the React render process to know when to memoize and when not to: React Render Tutorial.

3. Your use case

In your case, using useCallback for onUploadPress is waste of performance, beacause pickPhotoFromLibrary, an unmemoized function is in the dependency array. It would be a waste of performance also if TouchableOpacity is not memoized with React.memo, which I'm not sure it's.

yousoumar
  • 7,434
  • 4
  • 9
  • 33
  • "*It [is] worth it if your function is really long or does a lot of calculation*" - for `useMemo`, yes, for `useCallback`, no. – Bergi Feb 25 '22 at 11:59
  • The creation of function objects, and the comparison of functions by object identity, does not depend on how long the function is or how much work it does. (Btw it's not React that creates the function, it's the js engine) – Bergi Feb 25 '22 at 12:15
  • 1
    Didn't know that before. I edited my answer. Thank you. Though when I say simpler for React I mean the engine. – yousoumar Feb 25 '22 at 12:45
0

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 try to minimize the re-rendering process by watching some variables' value changes, then it decides to re-render on 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 useCalback in.

  • Example 1: When the function is one of the dependencies array of the useEffect.
function Component(){
  const [state, setState] = useState()
  
  // Should use `useCallback`
  function handleChange(input){
    setState(...)
  }

  useEffect(()=>{
    handleChange(...)
  },[handleChange])

  return ...
}
  • Example 2: When the function is being passed to one of the children components. Especially when it is being called on their 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"
}
  • Example 3: When you use 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 unmount 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.

Ahmed Tarek
  • 168
  • 2
  • 7