After this lesson, you will be able to: Build, compose, and test custom hooks.
Custom hooks are how you extract reusable logic. The senior React signal.
Prefix with 'use'; can call other hooks.
function useLocalStorage<T>(key: string, initial: T): [T, (v: T) => void] {const [value, setValue] = useState<T>(() => {if (typeof window === "undefined") return initial;const stored = localStorage.getItem(key);return stored ? JSON.parse(stored) : initial;});useEffect(() => {localStorage.setItem(key, JSON.stringify(value));}, [key, value]);return [value, setValue];}// Useconst [theme, setTheme] = useLocalStorage("theme", "light");
Useful in search inputs.
function useDebouncedValue<T>(value: T, delay = 300): T {const [debounced, setDebounced] = useState(value);useEffect(() => {const t = setTimeout(() => setDebounced(value), delay);return () => clearTimeout(t);}, [value, delay]);return debounced;}// Useconst [query, setQuery] = useState("");const debounced = useDebouncedValue(query, 300);useEffect(() => {if (debounced) fetchResults(debounced);}, [debounced]);
renderHook from @testing-library/react.
import { renderHook, act } from "@testing-library/react";import { useToggle } from "./useToggle";it("toggles", () => {const { result } = renderHook(() => useToggle());expect(result.current[0]).toBe(false);act(() => result.current[1]());expect(result.current[0]).toBe(true);});
Not prefixing with `use`. ESLint can't check hooks rules without the prefix. Conditional hook calls inside the custom hook. Same rule, same order every call. Returning new object/array references every render. Breaks downstream memoization. Over-extracting. One-time logic doesn't need a hook.
Pick the technical answer.
Sign in and purchase access to unlock this lesson.