DEV Community

Cover image for useCallback: Guide, use-cases, examples
alakkadshaw
alakkadshaw

Posted on • Originally published at deadsimplechat.com

useCallback: Guide, use-cases, examples

This is an easy to read guide, where we will cover

  • What is React useCallback?

  • When to use React useCallback? (use-cases)

  • Real world examples of useCallback

  • TroubleShooting Tips

What is React useCallback

useCallback is a React Hook. (Hooks are JS functions that let's you add additional pre-built functionality to your component)

useCallback is a hook that lets you cache a function definition between renders.

useCallback does not call the function and cache the return value. It caches the function definition itself.

useCallBack Reference

useCallback(function, [dependencies]);

As useCallback is a react Hook you need to call it at the top level of your component

import { useCallback } from 'react';

export default function ReactCommentsSection({ postId, username }) {
  const handleCommentSubmission = useCallback((Text) => {
    post('/post/' + postId + '/comment', {
      username,
      comment: commentText,
    });
  }, [postId, username]);
}
Enter fullscreen mode Exit fullscreen mode

Parameters

fn : (function to memorize)

This is the function that you want to cache.

This function can take any arguments and can return any data

The React will return this function definition (but not call it) during the first render.

On the next renders:

  • If the dependencies are not changed then the react will give you the same cached function

  • Else if, dependencies have changed the react will give you the current function that you have passed it. That is if the dependencies have changed.

Dependencies

An array of reactive values that the function depends on or are referenced by the  fn

Reactive values are props, state and all the variables/ functions that are inside your component

The react will do a shallow check to with the Object.is method to check if the dependencies have changed

You can use code linter to verify if all the dependencies are listed in the dependencies array

The list of dependencies should remain constant and will be written in line in an array like [dep1, dep2, dep3]

What does use callback Return

  • initial Render: On initial render the useCallback hook returns the function that you have passed it

  • Subsequent Renders :

  • If the dependencies have not changed, it will return the cached function

  • If the dependencies have changed, it will return the new function, that is the function you have passed during this render

Caveat

Always declare useCallback at the top of your component because it is a hook

useCallback should never be used inside loops or conditionals. If you need to use it inside loops or conditions, extract the code into a new component

Cache Management

  • React will not throw away a cached function unless there is a very good reason to do that

  • like: during development react will through a cached function, if you edit the component

  • If a component suspends the during initial mounting phase then the react will discard the cached

Examples and Use-cases

Use-Case : Skipping Re- rendering of components for performance optimization

1.  un-possessory re-renders

When developing large-scale applications or applications with complex logic, it becomes necessary to optimize performance.

Because of resource limitations, the logic or the scale of component and prop drilling can cause the UI to not perform optimally

Caching the Values or Function

One of the methods of optimizing performance is caching the values that require a lot of computation and that don't change much from one re-render to another

you can use the useMemo for caching the values, but what about functions?

As {} notation creates a new Object, the function notation like function () {} or () => {} creates a new function

Normally this isn't an issue but creating new function on every re render defeats the purpose of caching

So, we need to cache the functions that we are passing down as props here useCallback comes in handy

Example 1 : The Comment section

Consider a comment section on a website. Here we have CommentSection Component

import { useCallback } from 'react';

export default function CommentSection({ userName, userToken, postId, }) {
  const handleCommentSubmission = useCallback((textOfTheComment) => {
    post('/post/' + postId + '/comment', {
      username,
      comment: textOfTheComment,
      token: userToken,
    });
  }, [postId, userName, userToken]);
}
Enter fullscreen mode Exit fullscreen mode

Here we have a CommentSection Component. Our component takes three props : 1. userName, userToken, and postId

Then we have the handleCommentSection function that has the useCallback hook to which checks if any of the dependencies in the dependencies array has changed

the dependencies are : [postId, userName, userToken]

If the dependencies have not changed then the useCallback returns the cached function otherwise the useCallback returns the new function

When to use useCallback vs useMemo?

Both useCallback and useMemo are used to cache something to optimize performance

useMemo is used to cache values and useCallback is used to cache function definitions

So, as to prevent unnecessary re-renders

Difference of useMemo with useCallback

The useMemo hook takes a function and a dependency array as arguments

The useMemo calls the function and caches its returned value, the function is then only called when any of the dependencies change

const ComponentCompute = useMemo(() => {
  return calculateReq(product);
}, [product]);
Enter fullscreen mode Exit fullscreen mode

here the returned value of calculateReq will be cached and this will only be re-calculated when the product changes

useCallback also takes a function and a dependency array, but instead of caching the return value of the function it caches the function declaration itself

useMemo

  const requirements = useMemo(() => { // Calls your function and caches its result   
 return computeRequirements(product);  }, [product]);
Enter fullscreen mode Exit fullscreen mode

useCallback

import { useCallback } from 'react';

export default function CommentSection({ userName, userToken, postId, }) {
  const handleCommentSubmission = useCallback((textOfTheComment) => {
    post('/post/' + postId + '/comment', {
      username,
      comment: textOfTheComment,
      token: userToken,
    });
  }, [postId, userName, userToken]);
}
Enter fullscreen mode Exit fullscreen mode

When the handleCommentSubmission function gets called, useMemo calls the cached version and that remains the same across renders except when one of the dependencies change

If you are familiar with useMemo then you can think of useCallback as a wrapper around useMemo with built in functionality

you can use useMemo instead of useCallback by adding a callback function like so

function useCallback(fn, dependencies) {
  return useMemo(() => fn, dependencies);
}
Enter fullscreen mode Exit fullscreen mode

When to use useCallback

It depends on what type of app or website you have. If you have an app that is more like a website, that is you are changing Big parts or your UI at once then memorization of caching is not that useful

And if you only need to change small parts of your overall UI like in a drawing application or some other app

Where you load a whole lot of UI at once and then need to make small changes to the UI then caching would be a good strategy

Here are a few cases where useCallback is a good idea'

  1. If you are using useMemo in a component and you want to pass a function to that component as prop.

  2. If you are using the function inside other React Hooks. For example another function wrapped in useCallback depends on it you depend on this function from useEffect

Note: The useCallback does not prevent from creation a function. the function is always created and that is all good, React just ignores the created function and provides you with the cached version instead

Making Memorization and caching unnecessary by following a few principles:

  1. Wrap a component visually

When a component is visually wrapping other components like a big web page component containing smaller UI components like buttons etc

Let it accept JSX as its children. like so,

<div>
  <img />
    <button />
</div>
Enter fullscreen mode Exit fullscreen mode

Then if the wrapper component updates react know not to update the child components.

2. Local State

Always prefer local state and do not lift the state. Only lift the state when necessarily required.

So this importantly with short lived data like when a user is hovering over a  button or there is a small form

3. Keeping the rendering logic pure

The component should always be pure. Meaning for a given input props the output should always remain the same

or re-rendering the component should never give different outputs

Fix the bug instead of caching the component

4. Avoid unnecessary effects that update state

Effects are updates that the application asks react to perform after the UI update is over. However if there are lots of updates then it makes the app run in circles and causes performance issues

hence, only use updates when necessary

5. Removing unnecessary dependencies from your effects

instead of caching the values and the function declaration, you can try to reduce the dependencies like move out some objects or function

IF the interaction is still laggy use the React dev tools profiler to find out which components can benefit from memorization

The difference between useCallback and declaring a function directly

  1. Declaring a function directly

Directly declaring a function inside a component, creates a new instance of the function every time the component re renders. This is because as {} creates a new Object a new functional declaration like function (){} or ()=>{} creates a new function

function SomeComponent(props) {
  function handleClick() {
    console.log('Somebody clicked the button');
  }

  return <button onClick={handleClick}>Click the button     </button>;
}
Enter fullscreen mode Exit fullscreen mode

Here everytime the SomeComponent is rendered on screeen a new handleClick button is created.

2. useCallback

the useCallback hook memorizes the function defination itself. Now, when the SomeComponent is rendered on the screen the useCallback gives you the cached version of the handleClick method

import { useCallback } from 'react';

function SomeComponent(props) {
  const handleClick = useCallback(() => {
    console.log('user clicked button');
  }, []);  // function get recreated when dependencies change, here the array is empty meaning the function will never be recreated

  return <button onClick={handleClick}>Click the Button!</button>;
}
Enter fullscreen mode Exit fullscreen mode

Using the useCallback the handleClick method is only created once and cached by the react and the cached copy will always be used thus improving performance

This article is brought to you by DeadSimpleChat, Chat API and SDK for your website and apps.

Main Difference

  • Directly creating a function will create a new instance of the function every time the component re-renders

  • useCallback will cache the function and reuse the function definition thus giving the cached copy every time

Updating state from a memoized callback

In React you can update the state of a component using the setState function.

There are two options when you need to update the state based on the previous state

  1. Reading the current state from inside the callback

  2. using an updater function

Reading the current state from inside the callback

const handleTodo = useCallback((todoText) => {
  const newTodo = { id: nextId++, todoText };
  setTodos([...todos, newTodo]);
}, [todos]);
Enter fullscreen mode Exit fullscreen mode

Here

  • We are memorizing or caching the handleTodo function with useCallback

  • the todos state variable is specified as a dependency because we are reading the state directly

  • Here every time the todos change a new version of handleTodo will be created

using an updater function without direct dependency on state

const handleAddTodo = useCallback((text) => {
  const newTodo = { id: nextId++, text };
  setTodos(todos => [...todos, newTodo]);
}, []);
Enter fullscreen mode Exit fullscreen mode
  • Here instead of reading the todos inside of reading the todos directly and hence creating the nessissity of adding it as a dependency in the the dependencies array of useCallback

  • we are providing an updater function.  This updater func takes the previous todos as an argument and calculates the new todos state

  • Thus the benefit here is that the handleTodo now no longer needs to update whenever todos changes thus using the cached version more and improving the performance

Preventing an Effect from firing too often

Many times you have use the useEffect hook and you need to call a function inside the hook

This creates a problem, every value must be declared in the dependency array.

To optimize the useEffect and component rendering for performance purposes you can wrap the function that you are calling from useEffect into useCallback.

Optimizing a Custom Hook

Hooks are functions designed to encapsulate reusable functionality. This is why you can also create custom hooks of your own, this promotes code reusability and maintainability

  1. Need for optimization in custom Hooks

There is a need for optimization in custom hooks that return functions.

This is because these hooks can become problematic for components that have the returned function as a dependency.

The re creation of these functions at every render of the component could cause performance issues

2. useCallback for stable functions

 You can use the useCallback hook to cache these functions thus optimizing performance.

Conclusion

In this article, we learned about the useCallback react hook with use-cases and examples

I hope you liked the article. Thank you for reading.

Top comments (1)

Collapse
 
alakkadshaw profile image
alakkadshaw

Thanks for reading. I hope you liked the article. Let me know what you think in the comment section