█
LastWrite
  • > Curriculum
  • > Pricing
  • > For Educators
  • > About
  • > Contact
Log InGet Started

Questions, concerns, bug reports, or suggestions? We read every message, write to us at [email protected].

More ways to reach us →
LastWrite

Structured computer science lessons for aspiring developers and security professionals.

[email protected]

(201) 785-7951

Mon–Fri, 9 AM–5 PM EST

Learn

  • Curriculum
  • Pricing

Company

  • About
  • For Educators & Schools
  • Contact Us

Legal

  • Terms of Service
  • Privacy Policy
© 2026 LastWrite. All rights reserved.
Curriculum/Programming Languages/React (Standalone Deep Dive)/Custom Hooks
40 minIntermediate

Custom Hooks

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.

Prerequisites:Hooks in Depth

Custom hooks are just functions

Prefix with 'use'; can call other hooks.

tsx
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];
}
// Use
const [theme, setTheme] = useLocalStorage("theme", "light");

Composition example: useDebouncedValue

Useful in search inputs.

tsx
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;
}
// Use
const [query, setQuery] = useState("");
const debounced = useDebouncedValue(query, 300);
useEffect(() => {
if (debounced) fetchResults(debounced);
}, [debounced]);

Testing custom hooks

renderHook from @testing-library/react.

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

Common mistakes only experienced React devs avoid

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.

Quick Check

Why must custom hooks be called in the same order every render?

Pick the technical answer.

Sign in and purchase access to unlock this lesson.

Sign in to purchase
←Hooks in Depth
Back to React (Standalone Deep Dive)
State Management: Local vs Global→