Bitlyst

Why setState Doesn’t Cause Hydration Errors

🚀 The Core Idea

Hydration errors happen only if the first client render output doesn’t match the server-rendered HTML.
Most setState calls you write run after hydration is finished, so React treats them as normal updates, not as mismatches.


🕒 Timeline of hydration

  1. Server render

    • HTML is generated on the server and sent to the browser.
  2. Client boot

    • React runs the first client render and hydrates the existing DOM.
    • It compares this client render output with the server HTML.
    • If they differ, React shows a hydration mismatch warning.
  3. After hydration

    • useEffect callbacks run.
    • User events (clicks, inputs) happen.
    • Calling setState here triggers a normal re-render; React patches the DOM without comparing to the server again.

✅ So if your initial client render matches the server, later setState calls will not cause hydration errors.


❌ When you do get hydration errors

Hydration warnings occur if the first client render differs from the server HTML. Common causes include:

  • Non-deterministic values during render

    // BAD: server vs client will produce different numbers
    const id = Math.random();
    
  • Browser-only values in render

    // BAD: only available on client
    const width = window.innerWidth;
    
  • Different data fetching paths between server and client.


✅ Safe Patterns

1. Update inside useEffect

"use client";
import { useState, useEffect } from "react";

export default function Example() {
  const [theme, setTheme] = useState<string | null>(null); // null matches server
  useEffect(() => {
    setTheme(window.localStorage.getItem("theme")); // runs after hydration
  }, []);
  return <div>Theme: {theme ?? "system"}</div>;
}

2. Mounted flag for client-only UI

const [mounted, setMounted] = useState(false);
useEffect(() => setMounted(true), []);

if (!mounted) return null; // matches server's "nothing"
return <ClientOnlyUI />;

3. Disable SSR for the component

In Next.js, you can skip SSR entirely:

import dynamic from "next/dynamic";

const ClientComponent = dynamic(() => import("./ClientComponent"), { ssr: false });

4. Suppress warnings (last resort)

For known benign differences, use:

<span suppressHydrationWarning>{clientOnlyValue}</span>

🧠 Mental Model

  • Hydration compares only once: server HTML vs first client render.
  • setState after hydration = normal re-render, no mismatch.

So: make sure your first render is deterministic, and you’re safe.


How did you like this post?

👍0
❤️0
🔥0
🤔0
😮0
Why setState Doesn’t Cause Hydration Errors · Bitlyst