DEV Community

hsline918
hsline918

Posted on

A Quick dive into useCallback hook: concepts and how to use it

useCallback

1. Description:

Memorizes a function, recreating it only when its dependencies change.

2. Usage:

const example = useCallback(()=>{},[])
//memorize function ()=>{}, and depends on []
Enter fullscreen mode Exit fullscreen mode

3. What does it try to solve?

When a component re-renders, every function inside it is recreated. If such a function is passed to a child component as props, it’s considered to be a prop change, leading to unnecessary renders of the child component.

4. What causes the function to change (what it depends on)?

First, you need to consider the function’s design intention. Analyze its logic, why it designs that way and be aware of whether the function captures variables from the component’s scope (closure trap).
Here are two examples:

First, a simple example:

import React, { useState, useCallback } from 'react';

function SearchComponent() {
  const [query, setQuery] = useState('');
  const [results, setResults] = useState([]);

  const handleSearch = useCallback(() => {
    console.log(`Searching for: ${query}`);
    setResults(['result1', 'result2']);
  }, [query]); // dependency

  return (
    <div>
      <input 
        value={query} 
        onChange={(e) => setQuery(e.target.value)} 
        placeholder=""
      />
      <button onClick={handleSearch}>search</button>
      // below is search result
      <ul>...</ul>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

It’s important to include the query in the dependency array to avoid closure traps.

Additionally, React considers the setter function from useState as stable. If you don’t need to dynamically change the setter function in the future (for extensibility), you generally don’t need to add it to the dependency array. However, state values themselves usually should be included.

Let’s discuss another best practice mentioned in the React official docs: We always want fewer dependencies for better function stability. When updating based on previous state, you can use the updater function method (e.g., setTodos(todos => […todos, newTodo]);). This prevents constant re-renders caused by the function always reading new state.

Here’s another example from the official documentation:

//haven't use updater function
function TodoList() {
  const [todos, setTodos] = useState([]);

  const handleAddTodo = useCallback((text) => {
    const newTodo = { id: nextId++, text };
    setTodos([...todos, newTodo]);
  }, [todos]);

//(Best practice)use updater function
function TodoList() {
  const [todos, setTodos] = useState([]);

  const handleAddTodo = useCallback((text) => {
    const newTodo = { id: nextId++, text };
    setTodos(todos => [...todos, newTodo]);
  }, []); // ✅ No need for the todos dependency
  // 

Enter fullscreen mode Exit fullscreen mode

Official doc

5. When is the best time to use useCallback? Why do some people say that overusing useCallback might lead to performance issues?

Using useCallback has its own performance overhead. However, if your project has deep nesting, where function references are passed down to many child components. Recreating a function could cause unnecessary re-renders in all child components. In such cases, useCallback can be used to optimize by maintaining function reference stability.

Although the cost of creating new functions is usually small, it can become a performance bottleneck in high-frequency rendering or large applications, which is where we want to use the hook.

6. How will useCallback go in the future?

The React team is constantly optimizing the rendering process and memory usage, so future versions of React may introduce more automatic optimization mechanisms.

Additional Infos:

1. Why does it seem that the function is the same, but child components still consider it changed?

This is because child components use shallow comparison to decide whether to re-render. When a parent component recreates a function, it creates a new reference. Even if the function content hasn’t changed, the child component will see it as a state change (because the memory address has changed).

2. Won’t constantly creating new functions cause memory issues?

Creating new functions does occupy new memory, but when a render cycle ends and old function references are no longer used, they’re marked for garbage collection. Modern JS engines have highly efficient garbage collection mechanisms.

That’s all for today. Thanks for watching!

Top comments (0)