Alwaysblue
Alwaysblue

Reputation: 11830

What is useCallback in React and when to use it?

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

Answers (3)

Ahmed Tarek
Ahmed Tarek

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.

  • 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 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

Youssouf Oumar
Youssouf Oumar

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. So useCallback is used to memoize it.

A memoized component with memo re-renders only if its state or props changes, not because its parent re-renders. And since normally a new version of that passed function as props is created, when the parent re-renders, the child component gets a new reference, hence it re-renders. So useCallback 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

Yilmaz
Yilmaz

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

Related Questions