React Hooks Made Easy: A Step-by-Step Tutorial (Part 2)
Tags: react, javascript, beginners, frontend
Published: 2023-06-02 22:07:35

### Part 2: Optimize Performance with useMemo and useCallback
In [Part 1](https://dev.to/abosaiftaha/react-hooks-made-easy-a-step-by-step-tutorial-part-1-2p6g) of this article series, we explored the `useState`, `useEffect`, and `useRef` hooks, which are fundamental tools for managing state and handling side effects in React components. In Part 2, we will dive into two additional hooks: `useMemo` and `useCallback`. These hooks play a crucial role in optimizing the performance of your React applications by memoizing values and functions, respectively.

## useMemo

**Memoizing Values for Performance Optimization**

In React applications, rendering components can be an expensive operation, especially when dealing with complex computations or heavy data processing. The `useMemo` hook allows you to memoize the results of expensive calculations, ensuring that they are only recomputed when necessary. By storing the memoized value, subsequent renders can avoid redundant calculations, improving the overall performance of your application.

The `useMemo` hook accepts two arguments: a callback function and a dependency array. The callback function contains the computation logic, and the dependency array specifies the dependencies that trigger the re-computation of the memoized value.

### Example 1: Memoizing a Filtered List
Memoizing a Filtered List Suppose you have a list of items and want to apply a filter to display only the items that match a certain condition. By using `useMemo`, you can memoize the filtered list, preventing unnecessary re-filtering on each render.

```js
import React, { useState, useMemo } from 'react';

function ItemList({ items, filter }) {
  const filteredList = useMemo(() => {
    return items.filter(item => item.includes(filter));
  }, [items, filter]);

  return (
    <ul>
      {filteredList.map((item, index) => (
        <li key={index}>{item}</li>
      ))}
    </ul>
  );
}
```

In this example, the `ItemList` component receives a list of items and a filter as props. Using `useMemo`, we memoize the filtered list by applying the filter on the `items` array. The filtered list will only be recomputed if either the `items` or `filter` prop changes, preventing unnecessary filtering operations on each render.

### Example 2: Memoizing Expensive Computations
Memoizing Expensive Computations Sometimes, your component might perform computationally expensive operations, such as heavy calculations or API requests. By using `useMemo`, you can memoize the results of these computations and avoid recomputing them on each render.

```js
import React, { useState, useMemo } from 'react';

function ExpensiveComputation({ data }) {
  const result = useMemo(() => {
    // Perform expensive computation using 'data'
    // ...

    return computedResult;
  }, [data]);

  return <div>{result}</div>;
}
```

In this example, the `ExpensiveComputation` component receives `data` as a prop. The expensive computation is performed inside the `useMemo` callback function, and the result is memoized. The computation will only be rerun if the `data` prop changes, preventing unnecessary recomputations.

### Example 3: Memoizing Sorted Data
Memoizing Sorted Data When you have a list of items that need to be sorted, you can use `useMemo` to memoize the sorted list and avoid sorting it on each render.

```js 
import React, { useState, useMemo } from 'react';

function SortedList({ items }) {
  const sortedList = useMemo(() => {
    return [...items].sort();
  }, [items]);

  return (
    <ul>
      {sortedList.map((item, index) => (
        <li key={index}>{item}</li>
      ))}
    </ul>
  );
}
```

In this example, the `SortedList` component receives an `items` prop. Using `useMemo`, we memoize the sorted list by creating a copy of the `items` array using the spread operator `[...items]` and sorting it. The sorted list will only be recomputed if the `items` prop changes, avoiding unnecessary sorting operations on each render.

These examples demonstrate how `useMemo` can be used to optimize performance by memoizing computations, filtering operations, and sorting operations. It allows you to avoid unnecessary calculations and improve the rendering efficiency of your React components.

**But the question here, How should I decide if I need to `useMemo`?**
Deciding whether to use `useMemo` depends on the specific scenario and the performance requirements of your application. Here are a few considerations to help you determine if `useMemo` is needed:

1.  **Expensive Computations**: If your component performs computationally expensive operations, such as complex calculations, heavy data processing, or API requests, it can be beneficial to use `useMemo` to memoize the results. This way, you avoid recomputing the expensive operation on each render.
    
2.  **Props or State Dependency**: If the result of a computation depends on specific props or state values, and the computation is not affected by other unrelated changes, using `useMemo` can be helpful. By specifying the dependent values in the dependency array of `useMemo`, you ensure that the computation is rerun only when those values change.
    
3.  **Avoiding Unnecessary Re-renders**: If a calculation or transformation in your component doesn't need to be recalculated unless certain inputs change, `useMemo` can help prevent unnecessary re-renders. By memoizing the result, you can ensure that the computation is performed only when necessary.
    
4.  **List Filtering or Sorting**: When working with large lists and performing filtering or sorting operations, `useMemo` can be valuable. By memoizing the filtered or sorted result, you avoid repeated filtering or sorting on each render, which can improve performance.
    
5.  **Function References**: If your component uses callback functions that are passed as props, and you want to prevent unnecessary re-renders of child components, you can use `useCallback` in conjunction with `useMemo`. `useCallback` memoizes the function reference, ensuring that it doesn't change on each render unless its dependencies change.
    

Remember, it's essential to strike a balance between performance optimizations and code complexity. Only use `useMemo` when it provides a noticeable improvement in performance, and avoid overusing it for trivial computations or in situations where re-computation is negligible. Always measure and profile your application's performance to make informed decisions.


## useCallback

**Memoizing Functions for Performance Optimization**

In addition to memoizing values, React provides the `useCallback` hook for memoizing functions. Similar to `useMemo`, `useCallback` can help optimize performance by preventing unnecessary re-creation of functions, especially when they are passed as props to child components.

A callback function is a function that is passed as an argument to another function and is intended to be invoked at a later time or in response to a specific event. It allows for the implementation of dynamic and event-driven behavior in applications.

Callbacks are commonly used in React to handle events, asynchronous operations, and data fetching. For example, you might have a button component that takes a `onClick` prop, which expects a callback function to be invoked when the button is clicked. By memoizing the callback function with `useCallback`, you ensure that the same function instance is used unless its dependencies change, preventing unnecessary re-renders of the component that receives the callback.

By memoizing functions with `useCallback`, you optimize performance by avoiding the recreation of the same function on each render. This can be particularly useful when passing callbacks to child components, as it prevents those components from re-rendering when the parent component re-renders.

The `useCallback` hook accepts two arguments: a callback function and a dependency array. The callback function contains the function logic, and the dependency array specifies the dependencies that trigger the re-creation of the function.

Let's see an example that demonstrates the usage of `useCallback`:

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

function ParentComponent() {
  const [count, setCount] = useState(0);

  const increment = useCallback(() => {
    setCount((prevCount) => prevCount + 1);
  }, []);

  return (
    <div>
      <p>Count: {count}</p>
      <ChildComponent increment={increment} />
    </div>
  );
}

function ChildComponent({ increment }) {
  return (
    <button onClick={increment}>Increment</button>
  );
}
```

In this example, we have a `ParentComponent` that manages a `count` state using the `useState` hook. The `increment` function is defined inside the `ParentComponent` and passed as a prop to the `ChildComponent`.

By wrapping the `increment` function with `useCallback`, we ensure that it remains the same between renders as long as its dependencies (specified as an empty array `[]`) don't change. This prevents unnecessary re-renders of the `ChildComponent` when the `count` state updates.

The `increment` function is then used in the `ChildComponent` as an event handler for the "Increment" button. Clicking the button triggers the `increment` function, updating the `count` state in the `ParentComponent`.

By memoizing functions with `useCallback`, you can optimize the performance of your React components by preventing unnecessary re-creation of functions and minimizing unnecessary re-renders.

Certainly! Here's a code snippet showcasing the use cases of `useCallback`:

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

// Example 1: Callback as a Dependency
function CallbackDependency({ callback }) {
  const handleClick = useCallback(() => {
    // Use the callback function
    callback();
  }, [callback]);

  return <button onClick={handleClick}>Click me</button>;
}

// Example 2: Preventing Unnecessary Re-renders
function PreventRerenders() {
  const [count, setCount] = useState(0);

  const increment = useCallback(() => {
    setCount(count + 1);
  }, [count]);

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={increment}>Increment</button>
    </div>
  );
}

// Example 3: Memoizing a Function
function MemoizedFunction() {
  const memoizedFunction = useCallback(() => {
    // Perform expensive computation
    // ...

    return result;
  }, []);

  return (
    <div>
      <p>Result: {memoizedFunction()}</p>
    </div>
  );
}
```

In the code snippet above, we have three examples that demonstrate different use cases of `useCallback`:

1.  **Callback as a Dependency**: The `CallbackDependency` component takes a callback function as a prop and uses `useCallback` to memoize it. The memoized callback is then used in the `handleClick` function, ensuring that the same callback instance is used even if the component re-renders.
    
2.  **Preventing Unnecessary Re-renders**: The `PreventRerenders` component uses `useCallback` to memoize the `increment` function. By including `count` as a dependency, the `increment` function will only be recreated if `count` changes. This prevents unnecessary re-renders of child components that rely on the `increment` function.
    
3.  **Memoizing a Function**: The `MemoizedFunction` component demonstrates how `useCallback` can be used to memoize a computationally expensive function. By passing an empty dependency array `[]`, the memoized function is created once and reused throughout the component's lifecycle, avoiding unnecessary recomputations.
    

These examples illustrate the versatility of `useCallback` in optimizing performance and avoiding unnecessary function recreations in React components.

That leads to another main question:
**What is the diffrence between `useMemo` and `useCallback`? and when should I use each one of them?**

`useMemo` and `useCallback` are both hooks provided by React to optimize performance in different scenarios. While they have some similarities, there are key differences between them.

**useMemo:**

-   `useMemo` is used to memoize the result of a computation and cache it for future use.
-   It takes a function and a dependency array as arguments.
-   The function passed to `useMemo` is executed during rendering, and its return value is memoized.
-   The memoized value is only recalculated if any of the dependencies in the array change.
-   Use `useMemo` when you have a computationally expensive operation or a value that can be derived from other dependencies and you want to avoid recomputing it on every render.

**useCallback:**

-   `useCallback` is used to memoize a callback function and prevent unnecessary re-creations.
-   It takes a function and a dependency array as arguments.
-   The function passed to `useCallback` is memoized, meaning the same instance of the function is returned as long as the dependencies do not change.
-   Use `useCallback` when you need to pass a callback function to child components and want to avoid unnecessary re-renders of those components when the parent re-renders.

The key difference between `useMemo` and `useCallback` is their use cases. While `useMemo` is used for memoizing a computed value, `useCallback` is used for memoizing a callback function.

Here's a rule of thumb to help you decide when to use each one:

-   Use `useMemo` when you want to memoize a value or the result of a computation. This is useful when the computation is expensive and you want to avoid redundant calculations.
-   Use `useCallback` when you want to memoize a callback function. This is helpful when passing callbacks to child components to prevent unnecessary re-renders.

In summary, `useMemo` is used for memoizing values or computations, while `useCallback` is used for memoizing callback functions to optimize performance.

## Conclusion

In this article, we explored the `useMemo` and `useCallback` hooks, which are powerful tools for optimizing the performance of your React applications. By memoizing values and functions, respectively, you can avoid redundant calculations and unnecessary re-creation of functions, resulting in more efficient and performant components.

Understanding and utilizing these hooks in your React projects will enable you to write high-performance applications that deliver a smooth and responsive user experience. Incorporate `useMemo` and `useCallback` into your development workflow and harness their benefits to optimize the performance of your React components.

Stay tuned for Part 3, where we will explore the `useContext` and `useReducer` hooks, which are powerful tools for managing global state and complex state transitions in React applications.

Happy coding!