ReactHooksAdvanced

Advanced React Hooks Patterns

December 19, 2025
4 min

Learn advanced patterns for React Hooks such as custom hooks, useReducer for complex state, and optimization techniques that developers rarely know about.

Advanced React Hooks Patterns

Introduction

React Hooks have revolutionized the way we write React components since their introduction in version 16.8. However, many developers still use hooks only at a surface level, without realizing the powerful patterns they enable.

This article explores advanced React Hooks patterns that can significantly improve the quality, performance, and scalability of your React applications.


Powerful Custom Hooks

  • useFetch with Cache Management

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

const useFetch = (url, options = {}) => {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);
  const cache = useRef({});

  useEffect(() => {
    if (!url) return;

    const fetchData = async () => {
      setLoading(true);
      setError(null);

      // Cache mechanism
      if (cache.current[url]) {
        setData(cache.current[url]);
        setLoading(false);
        return;
      }

      try {
        const response = await fetch(url, options);
        if (!response.ok) throw new Error("Network response was not ok");

        const result = await response.json();
        cache.current[url] = result;
        setData(result);
      } catch (err) {
        setError(err.message);
      } finally {
        setLoading(false);
      }
    };

    fetchData();
  }, [url]);

  return { data, loading, error };
};

2. useLocalStorage with Serialization

import { useState } from "react";

const useLocalStorage = (key, initialValue) => {
  const [storedValue, setStoredValue] = useState(() => {
    try {
      const item = window.localStorage.getItem(key);
      return item ? JSON.parse(item) : initialValue;
    } catch (error) {
      console.error(`Error reading localStorage key "${key}":`, error);
      return initialValue;
    }
  });

  const setValue = (value) => {
    try {
      const valueToStore = value instanceof Function ? value(storedValue) : value;
      setStoredValue(valueToStore);
      window.localStorage.setItem(key, JSON.stringify(valueToStore));
    } catch (error) {
      console.error(`Error setting localStorage key "${key}":`, error);
    }
  };

  return [storedValue, setValue];
};

useReducer for Complex State Management

Advanced Todo App with useReducer

const todoReducer = (state, action) => {
  switch (action.type) {
    case "ADD_TODO":
      return {
        ...state,
        todos: [
          ...state.todos,
          {
            id: Date.now(),
            text: action.payload,
            completed: false,
            createdAt: new Date().toISOString(),
          },
        ],
      };

    case "TOGGLE_TODO":
      return {
        ...state,
        todos: state.todos.map((todo) =>
          todo.id === action.payload
            ? { ...todo, completed: !todo.completed }
            : todo
        ),
      };

    case "DELETE_TODO":
      return {
        ...state,
        todos: state.todos.filter((todo) => todo.id !== action.payload),
      };

    case "SET_FILTER":
      return { ...state, filter: action.payload };

    case "BULK_UPDATE":
      return {
        ...state,
        todos: state.todos.map((todo) => ({
          ...todo,
          ...action.payload.updates,
        })),
      };

    default:
      return state;
  }
};

const useTodo = () => {
  const [state, dispatch] = useReducer(todoReducer, {
    todos: [],
    filter: "all",
  });

  const filteredTodos = state.todos.filter((todo) => {
    switch (state.filter) {
      case "completed":
        return todo.completed;
      case "active":
        return !todo.completed;
      default:
        return true;
    }
  });

  return {
    todos: filteredTodos,
    addTodo: (text) => dispatch({ type: "ADD_TODO", payload: text }),
    toggleTodo: (id) => dispatch({ type: "TOGGLE_TODO", payload: id }),
    deleteTodo: (id) => dispatch({ type: "DELETE_TODO", payload: id }),
    setFilter: (filter) => dispatch({ type: "SET_FILTER", payload: filter }),
  };
};

Optimization Techniques That Are Rarely Known

1. useMemo with Deep Comparison

import { useMemo, useRef } from "react";

const useDeepCompareMemo = (factory, dependencies) => {
  const ref = useRef();
  const serializedDeps = JSON.stringify(dependencies);

  return useMemo(() => {
    if (ref.current !== serializedDeps) {
      ref.current = serializedDeps;
      return factory();
    }
  }, [serializedDeps, factory]);
};

2. useCallback with Dependency Injection

import { useCallback, useEffect, useRef } from "react";

const useStableCallback = (callback, dependencies = []) => {
  const callbackRef = useRef(callback);

  useEffect(() => {
    callbackRef.current = callback;
  });

  return useCallback((...args) => {
    return callbackRef.current(...args);
  }, dependencies);
};

Advanced Pattern: Compound Components with Context

import { createContext, useContext, useState } from "react";

const AccordionContext = createContext();

const Accordion = ({ children, multiple = false }) => {
  const [openItems, setOpenItems] = useState([]);

  const toggleItem = (id) => {
    setOpenItems((prev) =>
      multiple
        ? prev.includes(id)
          ? prev.filter((i) => i !== id)
          : [...prev, id]
        : prev.includes(id)
        ? []
        : [id]
    );
  };

  return (
    <AccordionContext.Provider value={{ openItems, toggleItem }}>
      {children}
    </AccordionContext.Provider>
  );
};

Pattern: State Machine with useReducer

const createMachine = (config) => (state, action) => {
  const current = config.states[state.state];

  if (current?.transitions?.[action.type]) {
    const transition = current.transitions[action.type];
    return {
      state: transition.target,
      context:
        transition.action?.(state.context, action) ?? state.context,
    };
  }

  return state;
};

Best Practices and Pitfalls

1. Hooks Dependency Array

// ❌ Wrong
useEffect(() => {}, [props.user]);

// ✅ Correct
useEffect(() => {}, [props.user.id]);

2. Custom Hooks Composition

const useUserDashboard = (userId) => {
  const user = useUser(userId);
  const posts = useUserPosts(userId);
  const notifications = useUserNotifications(userId);

  const isLoading =
    user.loading || posts.loading || notifications.loading;

  return {
    user: user.data,
    posts: posts.data,
    notifications: notifications.data,
    isLoading,
  };
};

3. Error Handling with Hooks

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

const useErrorHandler = () => {
  const [error, setError] = useState(null);

  const handleError = useCallback((err) => {
    console.error("Application error:", err);
    setError(err);
  }, []);

  useEffect(() => {
    if (error) throw error;
  }, [error]);

  return handleError;
};

Conclusion

Advanced React Hooks patterns unlock powerful ways to build maintainable, performant, and scalable applications.

Key Takeaways

  • Custom hooks improve logic reuse and separation of concerns

  • useReducer simplifies complex state management

  • Advanced memoization boosts performance

  • Compound components enable flexible APIs

  • State machines create predictable state transitions

By mastering these patterns, you will become a more confident and effective React developer.