useIsHydrated

Detect hydration state for server-side rendering safe rendering.

useIsHydrated

useIsHydrated is a React hook that returns a boolean indicating whether the component has been hydrated on the client. It's essential for rendering client-only content in Server-Side Rendering (SSR) environments.

Use this hook when you need to render different content on the server vs client, or when you have client-only code that shouldn't run during SSR.


API

function useIsHydrated(): boolean

Returns false during server render and initial client render, then true after hydration completes.


Return value

Prop

Type


Features

  • SSR-safe: Prevents hydration mismatches by deferring client-only rendering
  • Automatic detection: Uses useSyncExternalStore for React 16/17/18 compatibility
  • No setup required: Works out of the box without configuration
  • Lightweight: Minimal overhead, doesn't require external state management

Usage

Conditional client-only rendering

import { useIsHydrated } from '@loke/ui/use-is-hydrated';

export function ClientOnlyContent() {
  const isHydrated = useIsHydrated();

  // Only render this on the client, after hydration
  if (!isHydrated) {
    return null; // or a skeleton/placeholder
  }

  return <div>This only renders after hydration</div>;
}

Using localStorage safely

import { useIsHydrated } from '@loke/ui/use-is-hydrated';
import { useEffect, useState } from 'react';

export function ThemeToggle() {
  const isHydrated = useIsHydrated();
  const [theme, setTheme] = useState('light');

  useEffect(() => {
    if (isHydrated) {
      // Safe to access localStorage now
      const saved = localStorage.getItem('theme');
      if (saved) setTheme(saved);
    }
  }, [isHydrated]);

  return <div>Current theme: {theme}</div>;
}

Show placeholder during hydration

import { useIsHydrated } from '@loke/ui/use-is-hydrated';

export function DynamicWidget() {
  const isHydrated = useIsHydrated();

  return isHydrated ? (
    <ComplexInteractiveWidget />
  ) : (
    <WidgetSkeleton />
  );
}

With Suspense boundaries

import { useIsHydrated } from '@loke/ui/use-is-hydrated';
import { Suspense } from 'react';

export function App() {
  const isHydrated = useIsHydrated();

  return (
    <div>
      <Header />
      <Suspense fallback={<Loading />}>
        {isHydrated ? <ClientContent /> : <ServerContent />}
      </Suspense>
    </div>
  );
}

Common patterns

Guard against window/document access

import { useIsHydrated } from '@loke/ui/use-is-hydrated';

export function WindowDependentComponent() {
  const isHydrated = useIsHydrated();

  // Safe to access window only after hydration
  const width = isHydrated ? window.innerWidth : 0;

  return <div>Window width: {width}</div>;
}

Delayed component initialization

import { useIsHydrated } from '@loke/ui/use-is-hydrated';
import { useEffect, useState } from 'react';

export function InitializableComponent() {
  const isHydrated = useIsHydrated();
  const [initialized, setInitialized] = useState(false);

  useEffect(() => {
    if (isHydrated) {
      // Initialize client-only state/subscriptions here
      const unsubscribe = subscribeToClientEvents();
      setInitialized(true);
      return unsubscribe;
    }
  }, [isHydrated]);

  return initialized ? <Content /> : <Placeholder />;
}

useIsHydrated vs useLayoutEffect

Both prevent SSR hydration mismatches, but they work differently:

HookWhen it runsUse case
useIsHydratedAfter hydration completesShow/hide UI, conditional rendering
useLayoutEffectAfter DOM paint, before browser paint (client only)DOM measurements, immediate state updates

Key difference:

  • useLayoutEffect throws an error in some SSR setups (Next.js recommends avoiding it)
  • useIsHydrated is explicitly designed for SSR, returning false on server and during initial render

Use useIsHydrated for safer, more explicit SSR handling.


Implementation details

The hook uses useSyncExternalStore (from the React 18 shim) to:

  1. Return false during server render
  2. Return false during initial client render (before hydration)
  3. Return true after hydration is complete

This ensures consistent behavior across React versions (16.8+) without manual hydration tracking.


Notes

  • The hook is zero-overhead for server rendering
  • Returning false during hydration is safe and expected
  • You can safely nest multiple uses of this hook
  • The returned value is stable after hydration (won't change back to false)

Import

import { useIsHydrated } from '@loke/ui/use-is-hydrated';