ðŸĶ–DinosaurReactArchitecturePatterns

React Hooks Deep Dive

Beyond useState and useEffect. How hooks work internally, common mistakes, and the patterns that separate senior engineers.

React Hooks Deep Dive

If an interviewer asks you about hooks and you start with useState, you're thinking too small. Senior engineers discuss hooks as a paradigm for composing stateful logic.

How Hooks Work Internally

React maintains a linked list of hooks for each component instance. Each call to a hook corresponds to a node in this list, in order.

This is why hooks have rules:

  • Don't call hooks conditionally — the list would get out of order
  • Don't call hooks in loops — same reason
  • Call hooks at the top level — React relies on call order being stable
Component renders → Hook 1 → Hook 2 → Hook 3
                    useState   useEffect  useMemo

If you add a conditional hook between renders, Hook 2 might read Hook 3's data. Everything breaks silently.

useEffect is Not componentDidMount

The mental model shift:

  • Class lifecycle: "Run this code when the component mounts/updates/unmounts"
  • Hooks: "Synchronize this side effect with these dependencies"
// Wrong mental model
useEffect(() => {
  fetchUser(id); // "Fetch when id changes"
}, [id]);
 
// Right mental model
useEffect(() => {
  // Keep this effect synchronized with `id`
  let cancelled = false;
  fetchUser(id).then(data => {
    if (!cancelled) setUser(data);
  });
  return () => { cancelled = true; };
}, [id]);

The cleanup function isn't "componentWillUnmount" — it runs before every re-execution of the effect.

Custom Hooks — The Real Power

Custom hooks are the primary abstraction for sharing stateful logic:

function useMediaQuery(query: string): boolean {
  const [matches, setMatches] = useState(false);
 
  useEffect(() => {
    const mql = window.matchMedia(query);
    setMatches(mql.matches);
 
    const handler = (e: MediaQueryListEvent) => setMatches(e.matches);
    mql.addEventListener('change', handler);
    return () => mql.removeEventListener('change', handler);
  }, [query]);
 
  return matches;
}
 
function useLocalStorage<T>(key: string, initialValue: T) {
  const [value, setValue] = useState<T>(() => {
    try {
      const stored = localStorage.getItem(key);
      return stored ? JSON.parse(stored) : initialValue;
    } catch {
      return initialValue;
    }
  });
 
  useEffect(() => {
    localStorage.setItem(key, JSON.stringify(value));
  }, [key, value]);
 
  return [value, setValue] as const;
}

The Hook Composition Pattern

Senior pattern: compose hooks from smaller hooks.

function useAuth() {
  const [user, setUser] = useLocalStorage('user', null);
  const isDesktop = useMediaQuery('(min-width: 1024px)');
 
  const login = useCallback(async (credentials) => {
    const user = await authAPI.login(credentials);
    setUser(user);
  }, [setUser]);
 
  const logout = useCallback(() => {
    setUser(null);
  }, [setUser]);
 
  return { user, login, logout, isDesktop };
}

Interview Signal

When discussing hooks, demonstrate:

  1. Internal understanding — linked list, call order invariant
  2. Correct mental models — synchronization, not lifecycle
  3. Composition — custom hooks as the abstraction boundary
  4. Pitfall awareness — stale closures, missing deps, race conditions