Technology · React

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.

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];
    }
  • Return state, setters, and helper functions from your hook.
    function useFetch(url) {
      const [data, setData] = useState(null);
      const [loading, setLoading] = useState(true);
      
      useEffect(() => {
        fetch(url)
          .then(r => r.json())
          .then(setData)
          .finally(() => setLoading(false));
      }, [url]);
      
      return { data, loading };
    }
  • Keep each hook focused on a single concern.

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.

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;
    }

Rules of Hooks

  • Only call hooks at the top level, never inside loops or conditions.
    // Good: hook at top level
    function MyComponent() {
      const [count, setCount] = useState(0);
      useEffect(() => { /* ... */ }, []);
      return <div>{count}</div>;
    }
    
    // Bad: hook inside condition
    function MyComponent() {
      if (someCondition) {
        const [count, setCount] = useState(0); // WRONG!
      }
    }
  • Only call hooks from React components or custom hooks.
  • Never call hooks conditionally since order must stay stable.
  • ESLint plugin catches violations automatically.

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;
    }

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

Warning: Each component that uses a custom hook gets its own isolated state instance — they don't automatically share state between them.

React Context PerformanceReact Event Handling