Back to roadmaps react Course

Practical Custom Hooks Examples

In this guide, we will build three highly practical, production-ready custom hooks to handle data fetching, local storage persistence, and search input debouncing.


1. Custom Hook: useFetch

This hook manages network request state, including loading progress and errors:

import { useState, useEffect } from "react";

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

  useEffect(() => {
    let isMounted = true;
    setLoading(true);

    fetch(url)
      .then((res) => {
        if (!res.ok) throw new Error("Network response was not ok");
        return res.json();
      })
      .then((jsonData) => {
        if (isMounted) {
          setData(jsonData);
          setError(null);
        }
      })
      .catch((err) => {
        if (isMounted) {
          setError(err.message);
        }
      })
      .finally(() => {
        if (isMounted) {
          setLoading(false);
        }
      });

    return () => {
      isMounted = false; // Cancel state updates on unmount
    };
  }, [url]);

  return { data, loading, error };
}

2. Custom Hook: useLocalStorage

This hook synchronizes a state variable with the browser local storage automatically:

import { useState, useEffect } from "react";

export function useLocalStorage(key, initialValue) {
  // Read existing value from local storage or fallback to initialValue
  const [value, setValue] = useState(() => {
    try {
      const stored = localStorage.getItem(key);
      return stored ? JSON.parse(stored) : initialValue;
    } catch (e) {
      return initialValue;
    }
  });

  // Synchronize changes to local storage
  useEffect(() => {
    try {
      localStorage.setItem(key, JSON.stringify(value));
    } catch (e) {
      console.error("Local storage sync error:", e);
    }
  }, [key, value]);

  return [value, setValue];
}

3. Custom Hook: useDebounce

Use this hook to delay processing rapid state changes (for example, waiting for a user to finish typing in a search bar before triggering an API request):

import { useState, useEffect } from "react";

export function useDebounce(value, delay) {
  const [debouncedValue, setDebouncedValue] = useState(value);

  useEffect(() => {
    // Set a timeout to update the debounced value
    const handler = setTimeout(() => {
      setDebouncedValue(value);
    }, delay);

    // Clean up the timeout if the value changes before delay expires
    return () => {
      clearTimeout(handler);
    };
  }, [value, delay]);

  return debouncedValue;
}
Published on Last updated: