Skip to content
intermediate Phase 8 · React

Custom Hooks

Extract reusable logic into custom hooks for cleaner component code.

45m
0 problems
Topic Progress 0%

Creating Custom Hooks

Creating Custom Hooks

Custom hooks are functions that start with "use" and can call other hooks.

Basic Custom Hook

import { useState, useEffect } from "react";

function useLocalStorage(key, initialValue) {
  const [value, setValue] = useState(() => {
    const stored = localStorage.getItem(key);
    return stored ? JSON.parse(stored) : initialValue;
  });

  useEffect(() => {
    localStorage.setItem(key, JSON.stringify(value));
  }, [key, value]);

  return [value, setValue];
}

// Usage
function App() {
  const [name, setName] = useLocalStorage("name", "");

  return (
    <input
      value={name}
      onChange={(e) => setName(e.target.value)}
      placeholder="Enter name"
    />
  );
}

Data Fetching Hook

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

  useEffect(() => {
    const controller = new AbortController();

    async function fetchData() {
      try {
        setLoading(true);
        const response = await fetch(url, { signal: controller.signal });
        if (!response.ok) throw new Error("Failed to fetch");
        const json = await response.json();
        setData(json);
      } catch (err) {
        if (err.name !== "AbortError") {
          setError(err.message);
        }
      } finally {
        setLoading(false);
      }
    }

    fetchData();

    return () => controller.abort();
  }, [url]);

  return { data, loading, error };
}

// Usage
function UserProfile({ userId }) {
  const { data: user, loading, error } = useFetch(`/api/users/${userId}`);

  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error: {error}</div>;
  return <div>{user.name}</div>;
}

Naming Conventions

Naming Conventions

Rules

  1. Must start with "use"
  2. Follow camelCase
  3. Be descriptive

Good Names

// Good
useLocalStorage
useFetch
useDebounce
useToggle
usePrevious
useWindowSize
useMediaQuery
useForm

// Bad
localStorageHook
fetchData
toggleState
previous

Naming Pattern

// Pattern: use + [Feature]
useAuth()
useTheme()
useCart()
useNotification()

// Pattern: use + [Action]
useToggle()
useDebounce()
useThrottle()
useInterval()

// Pattern: use + [Data]
useLocalStorage()
useSessionStorage()
useCookie()
useQuery()

File Naming

// hooks/
useAuth.js
useLocalStorage.js
useFetch.js
useDebounce.js

Or grouped:

// hooks/
auth/useAuth.js
storage/useLocalStorage.js
api/useFetch.js

Common Patterns

Common Patterns

useToggle

function useToggle(initialValue = false) {
  const [value, setValue] = useState(initialValue);

  const toggle = useCallback(() => setValue((prev) => !prev), []);
  const setTrue = useCallback(() => setValue(true), []);
  const setFalse = useCallback(() => setValue(false), []);

  return { value, toggle, setTrue, setFalse };
}

// Usage
function Modal() {
  const { value: isOpen, toggle, setFalse: close } = useToggle();

  return (
    <div>
      <button onClick={toggle}>Toggle</button>
      {isOpen && (
        <div className="modal">
          <p>Modal content</p>
          <button onClick={close}>Close</button>
        </div>
      )}
    </div>
  );
}

useDebounce

function useDebounce(value, delay) {
  const [debouncedValue, setDebouncedValue] = useState(value);

  useEffect(() => {
    const timer = setTimeout(() => {
      setDebouncedValue(value);
    }, delay);

    return () => clearTimeout(timer);
  }, [value, delay]);

  return debouncedValue;
}

// Usage
function SearchInput() {
  const [search, setSearch] = useState("");
  const debouncedSearch = useDebounce(search, 500);

  useEffect(() => {
    if (debouncedSearch) {
      fetchResults(debouncedSearch);
    }
  }, [debouncedSearch]);

  return <input value={search} onChange={(e) => setSearch(e.target.value)} />;
}

useWindowSize

function useWindowSize() {
  const [size, setSize] = useState({
    width: window.innerWidth,
    height: window.innerHeight
  });

  useEffect(() => {
    const handleResize = () => {
      setSize({
        width: window.innerWidth,
        height: window.innerHeight
      });
    };

    window.addEventListener("resize", handleResize);
    return () => window.removeEventListener("resize", handleResize);
  }, []);

  return size;
}

// Usage
function ResponsiveComponent() {
  const { width } = useWindowSize();
  return <div>{width < 768 ? <MobileView /> : <DesktopView />}</div>;
}

usePrevious

function usePrevious(value) {
  const ref = useRef();

  useEffect(() => {
    ref.current = value;
  }, [value]);

  return ref.current;
}

// Usage
function Counter() {
  const [count, setCount] = useState(0);
  const prevCount = usePrevious(count);

  return (
    <div>
      <p>Now: {count}, Before: {prevCount}</p>
    </div>
  );
}

useInterval

function useInterval(callback, delay) {
  const savedCallback = useRef();

  useEffect(() => {
    savedCallback.current = callback;
  }, [callback]);

  useEffect(() => {
    function tick() {
      savedCallback.current();
    }

    if (delay !== null) {
      const id = setInterval(tick, delay);
      return () => clearInterval(id);
    }
  }, [delay]);
}

// Usage
function Timer() {
  const [count, setCount] = useState(0);

  useInterval(() => {
    setCount((c) => c + 1);
  }, 1000);
}

Quiz

1. What must custom hook names start with?

Question 1 options

2. Can custom hooks call other hooks?

Question 2 options

3. What is the benefit of custom hooks?

Question 3 options

4. Where should custom hooks be defined?

Question 4 options

Flashcards

Question

What is a custom hook?

Answer

A function that starts with 'use' and can call other hooks

Question

Why use custom hooks?

Answer

To extract and reuse stateful logic

Question

What is the naming convention for custom hooks?

Answer

Start with 'use' and follow camelCase

Question

Can custom hooks return values?

Answer

Yes, they can return any value including arrays, objects, and functions

Revision Notes

Key Takeaways

  • 1. Custom hooks start with 'use'
  • 2. They can call other hooks
  • 3. Extract reusable stateful logic
  • 4. Follow naming conventions
  • 5. Return values and setter functions

Interview Tips

  • Show how to create a custom hook
  • Explain when to extract logic into a hook
  • Demonstrate common custom hook patterns

Cheat Sheet

Cheat Sheet

Custom Hook Structure

function useSomething(args) {
  const [state, setState] = useState(initial);
  // ... logic
  return [state, setState];
}

Common Patterns

useLocalStorage(key, initial)
useFetch(url)
useToggle(initial)
useDebounce(value, delay)
useWindowSize()
usePrevious(value)
useInterval(callback, delay)

Rules

  • Start with 'use'
  • Can call other hooks
  • Return values/functions
  • Extract reusable logic