Skip to content

Instantly share code, notes, and snippets.

@jsnanigans
Last active May 19, 2026 08:01
Show Gist options
  • Select an option

  • Save jsnanigans/6940daa7546e92543a9bf38b5fd1de0d to your computer and use it in GitHub Desktop.

Select an option

Save jsnanigans/6940daa7546e92543a9bf38b5fd1de0d to your computer and use it in GitHub Desktop.
Complete React 18/19 Hook Lifecycle Flow

Phase 1: RENDER (Component Function Execution)

Can be interrupted/restarted by React

function Component({ prop }) {
  console.log('1. Function body starts');

  // STATE HOOKS - Initialize or return current state
  const [state, setState] = useState(initial);      // Returns current state
  const [state, dispatch] = useReducer(fn, init);  // Returns current state

  // CONTEXT - Read context value (triggers re-render if changes)
  const value = useContext(MyContext);

  // REF - Returns stable object reference
  const ref = useRef(initial);

  // MEMOIZATION - Execute during render if deps changed
  const memoValue = useMemo(() => compute(), [deps]);
  const memoFn = useCallback(() => fn(), [deps]);

  // TRANSITION (React 18+) - Non-blocking state updates
  const [isPending, startTransition] = useTransition();

  // DEFERRED VALUE (React 18+) - Delay re-render of expensive child
  const deferredValue = useDeferredValue(value);

  // ID - Stable unique ID for SSR hydration
  const id = useId();

  // EXTERNAL STORE (React 18+) - Subscribe to external data
  const snapshot = useSyncExternalStore(subscribe, getSnapshot);

  // OPTIMISTIC STATE (React 19+) - Optimistic UI updates
  const [optimisticState, addOptimistic] = useOptimistic(state, updateFn);

  // ACTION STATE (React 19+, was useFormState) - Server actions
  const [state, formAction] = useActionState(action, initialState);

  // USE (React 19+) - Suspend on promise or read context
  const data = use(promise);  // Suspends component
  const value = use(context); // Alternative to useContext

  // DEBUG VALUE - No runtime effect (DevTools only)
  useDebugValue(value, format);

  console.log('2. Render complete, returning JSX');
  return <div>...</div>;
}

Phase 2: COMMIT - Pre-DOM Mutation

Synchronous, before React touches the DOM

useInsertionEffect(() => {
  console.log('3. BEFORE DOM mutations (inject styles)');
  // Used by CSS-in-JS libraries to inject styles
  return () => console.log('Cleanup: useInsertionEffect');
}, [deps]);

Phase 3: COMMIT - DOM Mutation

React commits changes to real DOM

4. React updates real DOM

Phase 4: COMMIT - Post-DOM, Pre-Paint

Synchronous, blocks browser paint

useLayoutEffect(() => {
  console.log('5. After DOM commit, BEFORE paint');

  // Measure DOM, prevent flicker
  const height = ref.current.offsetHeight;

  return () => console.log('Cleanup: useLayoutEffect');
}, [deps]);

useImperativeHandle(ref, () => ({
  console.log('5b. Expose imperative ref handle');
  focus: () => inputRef.current.focus()
}), [deps]);

Phase 5: BROWSER PAINT

6. Browser paints screen (visual update)

Phase 6: POST-PAINT EFFECTS

Asynchronous, non-blocking

useEffect(() => {
  console.log('7. After paint (data fetching, subscriptions)');

  // Async operations
  const timer = setTimeout(() => {}, 1000);

  return () => {
    console.log('Cleanup: useEffect');
    clearTimeout(timer);
  };
}, [deps]);

Full Timeline with All Hooks

Initial Mount:

┌─ RENDER PHASE (interruptible) ─────────────────────┐
│ 1. Function body executes                          │
│    - useState/useReducer (initialize)              │
│    - useContext (read)                             │
│    - useRef (create/return)                        │
│    - useMemo (compute)                             │
│    - useCallback (create)                          │
│    - useTransition (return state)                  │
│    - useDeferredValue (return value)               │
│    - useId (return ID)                             │
│    - useSyncExternalStore (subscribe)              │
│    - useOptimistic (return state) [React 19]       │
│    - useActionState (return state) [React 19]      │
│    - use (suspend or read) [React 19]              │
│    - useDebugValue (DevTools only)                 │
│ 2. Return JSX                                      │
└────────────────────────────────────────────────────┘

┌─ COMMIT PHASE (synchronous) ───────────────────────┐
│ 3. useInsertionEffect callbacks [React 18+]        │
│ 4. React commits to real DOM                       │
│ 5. useLayoutEffect callbacks                       │
│ 6. useImperativeHandle (expose refs)               │
└────────────────────────────────────────────────────┘

7. ═══ BROWSER PAINTS ═══

┌─ POST-PAINT (async) ───────────────────────────────┐
│ 8. useEffect callbacks                             │
└────────────────────────────────────────────────────┘

Update (Re-render):

┌─ RENDER PHASE ──────────────────────────────────────┐
│ 1. Function body executes                           │
│    - useState/useReducer (return current state)     │
│    - useMemo (re-compute if deps changed)           │
│    - useCallback (recreate if deps changed)         │
│    - ... all render-phase hooks ...                 │
│ 2. Return JSX                                       │
└─────────────────────────────────────────────────────┘

┌─ COMMIT PHASE ──────────────────────────────────────┐
│ 3. useInsertionEffect cleanup (if deps changed)     │
│ 4. useInsertionEffect callback (if deps changed)    │
│ 5. React commits DOM updates                        │
│ 6. useLayoutEffect cleanup (if deps changed)        │
│ 7. useLayoutEffect callback (if deps changed)       │
│ 8. useImperativeHandle (if deps changed)            │
└─────────────────────────────────────────────────────┘

9. ═══ BROWSER PAINTS ═══

┌─ POST-PAINT ────────────────────────────────────────┐
│ 10. useEffect cleanup (if deps changed)             │
│ 11. useEffect callback (if deps changed)            │
└─────────────────────────────────────────────────────┘

Unmount:

1. useInsertionEffect cleanup (all)
2. useLayoutEffect cleanup (all)
3. useEffect cleanup (all)
4. Component removed from DOM

Hook Categories by Timing

Hook Phase Timing
useState Render During function execution
useReducer Render During function execution
useContext Render During function execution
useRef Render During function execution
useMemo Render During function execution
useCallback Render During function execution
useTransition Render During function execution
useDeferredValue Render During function execution
useId Render During function execution
useSyncExternalStore Render During function execution
useOptimistic Render During function execution
useActionState Render During function execution
use Render During function execution (can suspend)
useDebugValue Render DevTools only
useInsertionEffect Commit Before DOM mutation
useLayoutEffect Commit After DOM, before paint
useImperativeHandle Commit After DOM, before paint
useEffect Post-paint After paint

React 19 Specific Changes

New Hooks:

  • use(promise) - Suspend on promises (replaces some Suspense patterns)
  • use(context) - Alternative to useContext (can be conditional)
  • useOptimistic(state, updateFn) - Optimistic UI updates
  • useActionState(action, initial) - Server actions (renamed from useFormState)

Example React 19 Flow:

function Component() {
  // Can be conditional (unlike useContext)!
  if (condition) {
    const theme = use(ThemeContext);
  }

  // Suspend on promise
  const data = use(fetchData()); // Suspends until resolved

  // Optimistic updates
  const [messages, addOptimisticMessage] = useOptimistic(
    messages,
    (state, newMsg) => [...state, newMsg]
  );

  // Server actions
  const [state, formAction] = useActionState(async (prev, formData) => {
    return await submitForm(formData);
  }, initialState);

  return <form action={formAction}>...</form>;
}

Special Cases

Strict Mode (Development):

  • Function body runs twice on mount
  • Effects run once (mount → cleanup → mount)

Suspense:

  • use(promise) suspends render, shows fallback
  • When resolved, re-renders from that hook

Concurrent Rendering:

  • Render phase can be interrupted/restarted
  • Commit phase is always synchronous and atomic

Quick Reference Timeline

function Component() {
  // 1. RENDER PHASE - during function execution
  adapter.renderStart();
  const memo = useMemo(() => compute(), [deps]);

  useInsertionEffect(() => {
    // 2. COMMIT - before DOM mutation (CSS injection)
  }, [deps]);

  useLayoutEffect(() => {
    // 3. COMMIT - after DOM, before paint (measurements)
  }, [deps]);

  useEffect(() => {
    // 4. POST-PAINT - after paint (async operations)
  }, [deps]);

  return <div>...</div>;
}

Dependency Array Rules:

  • [] - Run once on mount only
  • [deps] - Run on mount + when deps change
  • no array - Run on mount + every render
  • Cleanup runs before next effect (if deps changed) + on unmount
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment