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(): booleanReturns 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
useSyncExternalStorefor 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:
| Hook | When it runs | Use case |
|---|---|---|
useIsHydrated | After hydration completes | Show/hide UI, conditional rendering |
useLayoutEffect | After DOM paint, before browser paint (client only) | DOM measurements, immediate state updates |
Key difference:
useLayoutEffectthrows an error in some SSR setups (Next.js recommends avoiding it)useIsHydratedis explicitly designed for SSR, returningfalseon 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:
- Return
falseduring server render - Return
falseduring initial client render (before hydration) - Return
trueafter 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
falseduring 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';