React Custom Hooks: Encapsulating Reusable Stateful Logic

Before React Hooks were introduced in version 16.8, sharing stateful logic between components required complex patterns like Higher-Order Components (HOCs) or Render Props. Custom Hooks completely transformed code sharing by allowing you to extract state and effects into standard, testable JavaScript functions.

The 2 Rules of React Hooks

Whether using built-in hooks or writing custom hooks, you must adhere to these two fundamental rules enforced by the React compiler:

  1. Only Call Hooks at the Top Level: Never call hooks inside loops, conditional statements, or nested functions. This ensures hooks are called in the exact same order on every render.
  2. Only Call Hooks from React Functions: Call hooks only from React functional components or from within other custom hooks.

Creating a Real-World Custom Hook: useLocalStorage

This custom hook synchronizes any React state with the browser's persistent localStorage, automatically reading on mount and saving on update:

// src/hooks/useLocalStorage.js
import { useState, useEffect } from 'react';

export function useLocalStorage(key, initialValue) {
  // 1. Lazy state initialization: reads from localStorage once on mount
  const [value, setValue] = useState(() => {
    try {
      const storedItem = window.localStorage.getItem(key);
      return storedItem ? JSON.parse(storedItem) : initialValue;
    } catch (error) {
      console.error('Error reading localStorage:', error);
      return initialValue;
    }
  });

  // 2. Synchronize to localStorage whenever 'value' changes
  useEffect(() => {
    try {
      window.localStorage.setItem(key, JSON.stringify(value));
    } catch (error) {
      console.error('Error writing to localStorage:', error);
    }
  }, [key, value]);

  return [value, setValue];
}

Using useLocalStorage Inside Multiple Components:

Notice how clean and readable your component becomes:

import React from 'react';
import { useLocalStorage } from './hooks/useLocalStorage';

function UserPreferences() {
  // Acts just like useState, but persists across page refreshes!
  const [theme, setTheme] = useLocalStorage('user-theme', 'light');
  const [username, setUsername] = useLocalStorage('username', 'Guest');

  return (
    <div className={`theme-${theme}`}>
      <h3>Welcome, {username}!</h3>
      <button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
        Toggle Theme (Current: {theme})
      </button>
    </div>
  );
}

Next Step: Component Lifecycle Methods

Now let us understand how class component lifecycles map directly to modern hooks in React Component Lifecycle Methods.

Explore Related React JS Tutorials

Accelerate your React mastery by studying these tightly connected core tutorials:

useEffect Fundamentals →

Hook rules, dependencies, and lifecycle synchronization.

useState Hook →

Local state preservation within reusable functions.

useDebugValue for Custom Hooks →

Displaying labels for custom hooks in React DevTools.

Share Share on Facebook Share on Twitter Share on LinkedIn Pin on Pinterest Share on Stumbleupon Share on Tumblr Share on Reddit Share on Diggit

You may also like this!