Useful Cheatsheetsusefulcheatsheets.com
React Custom Hooks
Chapter 11 · Page 77
Intermediate

React Custom Hooks

Build reusable custom hooks to extract component logic and share stateful behavior.

TL;DR

  1. 01Extract stateful logic from components into reusable hook functions.
  2. 02Start every custom hook name with the word use.
  3. 03Call other hooks inside your hook to compose behavior.

Tips

  1. 01Return values from custom hooks in the same format as built-in hooks — use arrays for positional access or objects for named access.

Warnings

  1. 01Each component that uses a custom hook gets its own isolated state instance — they don't automatically share state between them.
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Custom Hooks
Chapter 11 · Page 78
Intermediate

React Custom Hooks

(continued)

Basic Structure

  • Write a custom hook as a regular function that calls React hooks.
    function useToggle(initialValue = false) {
      const [value, setValue] = useState(initialValue);
    
      const toggle = useCallback(() => {
        setValue(v => !v);
      }, []);
    
      return [value, toggle];
    }
    
  • Combine useState and useEffect to track a browser API value over time.
    function useDebouncedValue(value, delayMs = 300) {
      const [debounced, setDebounced] = useState(value);
    
      useEffect(() => {
        const timer = setTimeout(() => setDebounced(value), delayMs);
        return () => clearTimeout(timer); // cancel if value changes again
      }, [value, delayMs]);
    
      return debounced;
    }
    
  • Call your hook from a component just like a built-in hook.
    function SearchBox() {
      const [query, setQuery] = useState('');
      const debouncedQuery = useDebouncedValue(query, 400);
    
      useEffect(() => {
        if (debouncedQuery) searchApi(debouncedQuery);
      }, [debouncedQuery]);
    
      return <input value={query} onChange={e => setQuery(e.target.value)} />;
    }
    
  • Keep each hook focused on a single concern.
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Custom Hooks
Chapter 11 · Page 79
Intermediate

React Custom Hooks

(continued)

Naming Rules

  • Always start custom hook names with use, like useToggle.
    // Good: clearly indicates it's a hook
    function useToggle() { }
    function useFetch(url) { }
    function useLocalStorage(key) { }
    
    // Bad: doesn't follow naming convention
    function toggle() { }
    function fetch() { }
  • Use camelCase after the use prefix for consistency.
  • Naming triggers React ESLint rules and warnings.
  • Pick names that describe what the hook does.
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Custom Hooks
Chapter 11 · Page 80
Intermediate

React Custom Hooks

(continued)

Common Patterns

  • Build useToggle for managing boolean state.
    function useToggle(initial = false) {
      const [value, setValue] = useState(initial);
      const toggle = () => setValue(!value);
      return [value, toggle];
    }
    
    // Usage
    function Modal() {
      const [isOpen, toggleOpen] = useToggle(false);
      return (
        <>
          <button onClick={toggleOpen}>Open</button>
          {isOpen && <div>Modal content</div>}
        </>
      );
    }
  • Build usePrevious to track previous prop or state values.
    function usePrevious(value) {
      const ref = useRef();
      useEffect(() => {
        ref.current = value;
      }, [value]);
      return ref.current;
    }
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Custom Hooks
Chapter 11 · Page 81
Intermediate

React Custom Hooks

(continued)

Composing and Typing Hooks

  • Compose several built-in hooks inside one custom hook to build a higher-level abstraction.
    function useOnlineStatus() {
      const [isOnline, setIsOnline] = useState(navigator.onLine);
    
      useEffect(() => {
        const goOnline = () => setIsOnline(true);
        const goOffline = () => setIsOnline(false);
        window.addEventListener('online', goOnline);
        window.addEventListener('offline', goOffline);
        return () => {
          window.removeEventListener('online', goOnline);
          window.removeEventListener('offline', goOffline);
        };
      }, []);
    
      return isOnline;
    }
    
  • Build one custom hook on top of another instead of duplicating logic.
    function useSyncedField(key, initialValue) {
      const [value, setValue] = useLocalStorage(key, initialValue); // reuse an existing hook
      const isOnline = useOnlineStatus(); // compose a second hook
      return { value, setValue, isOnline };
    }
    
  • Type a generic custom hook so callers get the correct inferred return type.
    function useToggle<T = boolean>(initial: T): [T, () => void] {
      const [value, setValue] = useState(initial);
      const toggle = useCallback(() => setValue(v => !v as T), []);
      return [value, toggle];
    }
    
    const [isOpen, toggleOpen] = useToggle(false); // isOpen inferred as boolean
    
  • Test a custom hook in isolation with renderHook from @testing-library/react.
    import { renderHook, act } from '@testing-library/react';
    
    test('useToggle flips its value', () => {
      const { result } = renderHook(() => useToggle(false));
      act(() => result.current[1]()); // call the toggle function
      expect(result.current[0]).toBe(true);
    });
    
  • renderHook wraps your hook in a tiny test component so you never need a real UI to test it.
  • Keep composed hooks shallow — two or three layers deep is easier to trace than a long chain.
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Custom Hooks
Chapter 11 · Page 82
Intermediate

React Custom Hooks

(continued)

Sharing State Across Components

  • Each component gets its own state instance of a custom hook.
    function useCounter() {
      const [count, setCount] = useState(0);
      return [count, () => setCount(count + 1)];
    }
    
    function Component1() {
      const [count, increment] = useCounter();
      return <button onClick={increment}>{count}</button>;
    }
    
    function Component2() {
      const [count, increment] = useCounter();
      return <button onClick={increment}>{count}</button>;
    }
    // Each component has separate count state
  • Use Context API with custom hooks to share state.
    function useAuth() {
      const context = useContext(AuthContext);
      if (!context) throw new Error("useAuth needs provider");
      return context;
    }
Notes
Useful Cheatsheetsusefulcheatsheets.com
React Custom Hooks
Chapter 11 · Page 83
Intermediate

React Custom Hooks

(FAQ)

FAQ

Extract into a custom hook when the same stateful logic (fetching, form handling, subscriptions) appears in multiple components, or when a single component's logic grows complex enough to benefit from separation. Custom hooks let you test and reuse that logic independently of any UI.

No — hooks must be called at the top level of a function, never inside conditionals, loops, or nested functions. This rule applies inside custom hooks too, not just components.

You can't share state via a hook alone — each component calling the hook gets its own independent state instance. To share state, lift it to a common parent and pass it down, or use a context provider that the hook reads from internally.

Return an array (like useState) when consumers will typically rename the values and order is obvious; return an object (like useReducer's dispatch pattern or React Query) when there are many return values or names carry meaning. Mixing both in a single hook adds confusion.

No — a custom hook runs in the context of the component that calls it, so re-render behavior is identical to inlining the logic. The hook abstraction has no runtime overhead.