[React] What is useCallback?

·

3 min read

[React] What is useCallback?

useCallback is a React Hook that lets you cache a function definition between re-renders.


How to use useCallback?

useCallback(fn, dependencies)

import { useCallback } from 'react';

export default function ProductPage({ productId, referrer, theme }) {
  const handleSubmit = useCallback((orderDetails) => {
    post('/product/' + productId + '/buy', {
      referrer,
      orderDetails,
    });
  }, [productId, referrer]);

Parameters

  • fn: The function value that you want to cache. It can take any arguments and return any values. React will return (not call!) your function back to you during the initial render. On next renders, React will give you the same function again if the dependencies have not changed since the last render. Otherwise, it will give you the function that you have passed during the current render, and store it in case it can be reused later.

  • dependencies: The list of all reactive values referenced inside of the fn code. Reactive values include props, state, and all the variables and functions declared directly inside your component body.

Returns

On the initial render, useCallback returns the fn function you have passed.

During subsequent renders, it will either return an already stored fn function from the last render (if the dependencies haven’t changed), or return the fn function you have passed during this render.


When to use useCallback?

// index.tsx - before
import { useState } from 'react';
// shuffle func just shuffle the array
import { shuffle } from '@/utils';
import Search from './Search';

const allUsers = [
  'john',
  'alex',
  'george',
  'simon',
  'james',
];

interface DemoProps {}

export default function Demo({}: DemoProps) {
  const [users, setUsers] = useState(allUsers);

  const handleSearch = (text: string) => {
    const filteredUsers = allUsers.filter((user) =>
      user.includes(text),
    );
      setUsers(filteredUsers);
  };

  return (
    <div className='tutorial'>
      <div className='align-center mb-2 flex'>
        <button onClick={() => setUsers(shuffle(allUsers))}>
          Shuffle
        </button>
        <Search onChange={handleSearch} />
      </div>
      <ul>
        {users.map((user) => (
          <li key={user}>{user}</li>
        ))}
      </ul>
    </div>
  );
}
// Search.tsx
// memo(): skip re-rendering of components when the props are unchanged
import { memo } from 'react';

interface SearchProps {
  onChange: (text: string) => void;
}

function Search({ onChange }: SearchProps) {
  return (
    <input
      type='text'
      placeholder='Search users...'
      onChange={(e) => onChange(e.target.value)}
    />
  );
}

export default memo(Search);

In this case, everytime you click the Shuffle button, React re-renders Search component. Because in React functions are going to be considered different on every render, everytime you click Shuffle, React will consider handleSearch as a different function so React will re-render Search component since it receives handleSearch.

Since the handleSearch will continue to be the same function, there's no need to re-render Search component on every render. So we can just reuse the previous function in the next render.

// index.tsx - after
import { useCallback, useState } from 'react'; // useCallback added
import { shuffle } from '@/utils';
import Search from './Search';

const allUsers = [
  'john',
  'alex',
  'george',
  'simon',
  'james',
];

interface DemoProps {}

export default function Demo({}: DemoProps) {
  const [users, setUsers] = useState(allUsers);

  // More efficient!
  const handleSearch = useCallback(
    (text: string) => {
      const filteredUsers = allUsers.filter((user) =>
        user.includes(text),
      );
      setUsers(filteredUsers);
    },
    [],
  );

  return (
    <div className='tutorial'>
      <div className='align-center mb-2 flex'>
        <button onClick={() => setUsers(shuffle(allUsers))}>
          Shuffle
        </button>
        <Search onChange={handleSearch} />
      </div>
      <ul>
        {users.map((user) => (
          <li key={user}>{user}</li>
        ))}
      </ul>
    </div>
  );
}

Using useCallback can reduce unnecessary renders and allows application to operate more efficiently.

🚨 useCallback is going to memorize/freeze everything in the function at a certain point. So use dependency array to control when the function should be different. 🚨


Reference