Codepath

React Custom Hooks

Overview

Custom hooks let you extract and reuse stateful logic in React functional components. This guide covers what they are, why to use them, and how to create one.


What is a Custom Hook?

A custom hook is a JavaScript function that:

  • Starts with use (e.g., useCustomHook).
  • Uses React’s built-in hooks (like useState, useEffect) to manage state or side effects.
  • Returns values or functions for components to use.

It’s a way to share logic, not UI, across components.


Why Create a Custom Hook?

  • Reuse Logic: Avoid duplicating code (e.g., fetching data, managing forms).
  • Cleaner Components: Move complex logic out of components for better readability.
  • Encapsulation: Keep related state and behavior together.
graph TD
    A[Component] --> B[Custom Hook]
    B --> C[Reusable Logic]
    C --> D[State]
    C --> E[Side Effects]
    B --> F[Returns Values]
    F --> A

How to Create a Custom Hook?

1. Move Logic into a Reusable Function

Identify repetitive logic (e.g., fetching data) and extract it.

2. Name it with use

Prefix the function with use to follow React’s hook convention.

3. Use Built-in Hooks Inside

Leverage useState, useEffect, etc., within the custom hook.

4. Return State/Functions

Return what components need (e.g., data, setters).


Examples

Example 1: useFetch - Fetching Data

Here’s a custom hook to fetch data:

// useFetch.js
import { useState, useEffect } from 'react';

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

  useEffect(() => {
    setLoading(true);
    fetch(url)
      .then(res => res.json())
      .then(data => {
        setData(data);
        setLoading(false);
      });
  }, [url]); // Re-fetch if url changes

  return { data, loading };
}

// Usage in a component
function App() {
  const { data, loading } = useFetch('https://api.example.com/data');
  return loading ? <p>Loading...</p> : <p>{data.message}</p>;
}
  • Logic: Fetches data and tracks loading state.
  • Name: useFetch signals it’s a hook.
  • Hooks Used: useState for data/loading, useEffect for fetching.
  • Returns: { data, loading } for the component to use.

Example 2: useLocalStorage - Syncing State with Local Storage

When to Use: When you want to persist simple state (e.g., a user preference) in local storage.

// useLocalStorage.js
import { useState } from 'react';

function useLocalStorage(key, initialValue) {
  const [value, setValue] = useState(() => {
    return localStorage.getItem(key) || initialValue;
  });

  const setStoredValue = (newValue) => {
    setValue(newValue);
    localStorage.setItem(key, newValue);
  };

  return [value, setStoredValue];
}

// Usage
function ThemeSwitcher() {
  const [theme, setTheme] = useLocalStorage('theme', 'light');
  return (
    <button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
      Switch to {theme === 'light' ? 'Dark' : 'Light'}
    </button>
  );
}
  • Logic: Syncs state with local storage.
  • Returns: [value, setStoredValue]—the stored value and an updater.

Example 3: useCounter - Managing a Counter

When to Use: When you need a reusable counter (e.g., for likes, scores) in multiple places.

// useCounter.js
import { useState } from 'react';

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

  const increment = () => setCount(prev => prev + 1);
  const decrement = () => setCount(prev => prev - 1);
  const reset = () => setCount(initialCount);

  return { count, increment, decrement, reset };
}

// Usage
function Counter() {
  const { count, increment, decrement, reset } = useCounter(0);
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={increment}>+1</button>
      <button onClick={decrement}>-1</button>
      <button onClick={reset}>Reset</button>
    </div>
  );
}
  • Logic: Manages a numeric counter with actions.
  • Returns: { count, increment, decrement, reset }—the count and control functions.

References

Fork me on GitHub