React Custom Hooks: The Best Way to Share Logic

By Mohiuddin Murad
October 15, 2023
React
Hooks
Custom Hooks
State Management
React Custom Hooks: The Best Way to Share Logic

React Custom Hooks are reusable JavaScript functions whose names begin with "use" and that can call other Hooks (such as useState, useEffect). They provide a powerful way to extract and share stateful logic between components without altering the component hierarchy.

Why Use Custom Hooks?

  • Reusability: They allow you to reuse logic across multiple components, adhering to the DRY (Don't Repeat Yourself) principle.
  • Clean Code & Separation of Concerns: Custom Hooks help separate complex business logic from the UI rendering logic of a component, making the code cleaner, more readable, and easier to reason about.
  • Maintainability: With the logic centralized in one place, it becomes much easier to update, debug, and maintain.

Example 1: useFetch for Data Fetching

A common use case is creating a custom hook to handle data fetching, loading states, and errors.

import { useState, useEffect } from 'react';

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

  useEffect(() => {
    if (!url) return;
    
    const fetchData = async () => {
      setLoading(true);
      try {
        const response = await fetch(url);
        if (!response.ok) {
          throw new Error('Network response was not ok');
        }
        const result = await response.json();
        setData(result);
        setError(null);
      } catch (err) {
        setError(err);
        setData(null);
      } finally {
        setLoading(false);
      }
    };

    fetchData();
  }, [url]); // Reruns the effect if the URL changes

  return { data, loading, error };
}

// How to use it in a component
function UserProfile({ userId }) {
  const { data: user, loading, error } = useFetch(`https://api.example.com/users/${userId}`);

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

  return (
    <div>
      <h1>{user.name}</h1>
      <p>Email: {user.email}</p>
    </div>
  );
}

Example 2: useLocalStorage for Persistent State

This hook simplifies the process of synchronizing component state with the browser's local storage.

import { useState, useEffect } from 'react';

function useLocalStorage(key, initialValue) {
  // Get from local storage then
  // parse stored json or return initialValue
  const [storedValue, setStoredValue] = useState(() => {
    try {
      const item = window.localStorage.getItem(key);
      return item ? JSON.parse(item) : initialValue;
    } catch (error) {
      console.error(error);
      return initialValue;
    }
  });

  // Return a wrapped version of useState's setter function that ...
  // ... persists the new value to localStorage.
  const setValue = (value) => {
    try {
      // Allow value to be a function so we have same API as useState
      const valueToStore =
        value instanceof Function ? value(storedValue) : value;
      setStoredValue(valueToStore);
      window.localStorage.setItem(key, JSON.stringify(valueToStore));
    } catch (error) {
      console.error(error);
    }
  };

  return [storedValue, setValue];
}

// How to use it in a component
function ThemeSwitcher() {
    const [theme, setTheme] = useLocalStorage('theme', 'light');
    
    const toggleTheme = () => {
        setTheme(prevTheme => (prevTheme === 'light' ? 'dark' : 'light'));
    };

    return (
        <button onClick={toggleTheme}>
            Current theme: {theme}
        </button>
    );
}

Custom Hooks are a fundamental pattern in modern React development, promoting cleaner, more modular, and highly maintainable codebases.