3 min read

Advanced React Hooks: useMemo, useCallback, and Custom Hooks

Introduction: Beyond the Basics of React Hooks

React hooks revolutionized how we write functional components. But useState and useEffect are just the tip of the iceberg. This post dives into more advanced hooks like useMemo and useCallback, essential for performance optimization, and shows you how to create your own custom hooks to encapsulate reusable logic and keep your code DRY (Don’t Repeat Yourself).

useMemo: Memoizing Expensive Calculations

Imagine you have a component that performs a complex calculation every time it re-renders. If this calculation is expensive (e.g., processing large datasets, complex string manipulations), it can significantly impact performance. useMemo to the rescue!

useMemo memoizes the result of a function. It only re-runs the function if the dependencies in the dependency array change.

import React, { useMemo, useState } from "react";

function MyComponent() {
  const [count, setCount] = useState(0);
  const [data, setData] = useState([
    /* ... large dataset ... */
  ]);

  // Expensive calculation – only re-runs if `data` changes
  const processedData = useMemo(() => {
    console.log("Processing data..."); // For demonstration
    return expensiveDataProcessing(data);
  }, [data]);

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment Count</button>
      {/* Use processedData */}
    </div>
  );
}

function expensiveDataProcessing(data) {
  // Simulate an expensive operation
  let sum = 0;
  for (let i = 0; i < data.length; i++) {
    sum += data[i];
  }
  return sum;
}

Even though the count state updates (and the component re-renders), the expensiveDataProcessing function doesn’t re-run because data hasn’t changed. This saves precious processing time.

useCallback: Memoizing Functions

useCallback is similar to useMemo, but it memoizes functions instead of values. This is particularly useful when passing functions as props to child components that rely on referential equality to prevent unnecessary re-renders.

import React, { useState, useCallback } from "react";
import MyChildComponent from "./MyChildComponent";

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

  // Memoized callback – only changes if `count` changes
  const handleClick = useCallback(() => {
    console.log("Button clicked!", count);
  }, [count]);

  return (
    <div>
      <MyChildComponent onClick={handleClick} />
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
}

// MyChildComponent.jsx
import React, { memo } from "react";

function MyChildComponent({ onClick }) {
  console.log("Child rendered"); // Log when this component rendered.
  return <button onClick={onClick}>Click Me</button>;
}

export default memo(MyChildComponent);

The MyChildComponent is now only re-rendered when count changes because the memoized handleClick function only changes when count changes.

Custom Hooks: Abstracting Reusable Logic

Custom hooks allow you to extract reusable logic into separate functions, making your components cleaner and more maintainable. They’re like functions, but with the added superpower of using other hooks.

import React, { useState, useEffect } from "react";

function useFetch(url) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    async function fetchData() {
      try {
        const response = await fetch(url);
        const json = await response.json();
        setData(json);
      } catch (error) {
        setError(error);
      } finally {
        setLoading(false);
      }
    }

    fetchData();
  }, [url]);

  return { data, loading, error };
}

// Using the custom hook in a component
function MyComponent() {
  const { data, loading, error } = useFetch("some-api-endpoint");

  if (loading) {
    return <p>Loading...</p>;
  }

  if (error) {
    return <p>Error: {error.message}</p>;
  }

  return <div>{/* Render data */}</div>;
}

The useFetch custom hook encapsulates the logic for fetching data, handling loading and error states. This hook can be reused in multiple components.

Conclusion: Hooked on Performance and Reusability

useMemo, useCallback, and custom hooks are powerful tools for optimizing performance and writing cleaner, more maintainable React code. Master these advanced hooks, and you’ll be well on your way to becoming a React expert!