Client-side rendering APIs for modern React applications using concurrent rendering features.
Creates a React root for rendering React components inside a browser DOM element with concurrent features enabled.
/**
* Create a React root for concurrent rendering
* @param container - DOM element or DocumentFragment to render into
* @param options - Optional configuration for the root
* @returns Root object with render() and unmount() methods
*/
function createRoot(
container: Element | DocumentFragment,
options?: CreateRootOptions
): Root;
interface CreateRootOptions {
/** Enable React strict mode checks */
unstable_strictMode?: boolean;
/** Prefix for IDs generated by useId hook */
identifierPrefix?: string;
/** Handler for uncaught errors that escape all error boundaries */
onUncaughtError?: (error: Error, errorInfo: ErrorInfo) => void;
/** Handler for errors caught by error boundaries */
onCaughtError?: (error: Error, errorInfo: ErrorInfo) => void;
/** Handler for errors React can recover from automatically */
onRecoverableError?: (error: Error, errorInfo: ErrorInfo) => void;
/** Experimental callback for default transition indicator (can return cleanup function) */
onDefaultTransitionIndicator?: () => void | (() => void);
/** Experimental transition callbacks for React DevTools */
unstable_transitionCallbacks?: TransitionTracingCallbacks;
}
interface Root {
/** Render React elements into the root */
render(children: ReactNode): void;
/** Unmount the root and cleanup all resources */
unmount(): void;
}
interface ErrorInfo {
/** Stack trace of React components involved in the error */
componentStack?: string;
/** The error boundary component that caught the error (if onCaughtError) */
errorBoundary?: React.Component;
}Usage Examples:
import { createRoot } from 'react-dom/client';
// Basic usage
const root = createRoot(document.getElementById('root'));
root.render(<App />);
// With options
const root = createRoot(document.getElementById('root'), {
identifierPrefix: 'my-app-',
onUncaughtError(error, errorInfo) {
console.error('Uncaught error:', error);
console.error('Component stack:', errorInfo.componentStack);
// Report to error tracking service
},
onRecoverableError(error, errorInfo) {
console.warn('Recoverable error:', error);
// Log but don't necessarily show to user
}
});
root.render(<App />);
// Update rendered content
root.render(<App updatedProp="value" />);
// Cleanup when done
root.unmount();Key Features:
Migration from React 17:
// Old (React 17)
import ReactDOM from 'react-dom';
ReactDOM.render(<App />, document.getElementById('root'));
// New (React 18+)
import { createRoot } from 'react-dom/client';
const root = createRoot(document.getElementById('root'));
root.render(<App />);Notes:
render() for the first timerender() multiple times updates the existing renderunmount() removes all event handlers and state, and stops all pending workunmount(), you cannot use the root again; create a new root if neededAttaches React to server-rendered HTML inside a browser DOM element, preserving the existing markup and making it interactive.
/**
* Hydrate server-rendered HTML with React
* @param container - DOM element containing server-rendered content
* @param initialChildren - React elements matching the server content
* @param options - Optional configuration for hydration
* @returns Root object with render() and unmount() methods
*/
function hydrateRoot(
container: Element | Document,
initialChildren: ReactNode,
options?: HydrateRootOptions
): Root;
interface HydrateRootOptions extends CreateRootOptions {
/** Called when a Suspense boundary completes hydration */
onHydrated?: (suspenseBoundary: SuspenseBoundary) => void;
/** Called when a Suspense boundary is deleted during hydration */
onDeleted?: (suspenseBoundary: SuspenseBoundary) => void;
/** Form state from server rendering (for Server Actions) */
formState?: ReactFormState;
}
type SuspenseBoundary = Comment; // HTML comment node marking boundaryUsage Examples:
import { hydrateRoot } from 'react-dom/client';
// Basic hydration
hydrateRoot(document.getElementById('root'), <App />);
// With options
hydrateRoot(
document.getElementById('root'),
<App />,
{
onRecoverableError(error, errorInfo) {
console.error('Hydration error:', error);
// Log hydration mismatches
},
onHydrated(suspenseBoundary) {
console.log('Suspense boundary hydrated:', suspenseBoundary);
}
}
);
// Hydrating the entire document
hydrateRoot(document, <App />);Hydration with Server Actions:
import { hydrateRoot } from 'react-dom/client';
// Pass formState from server to enable Server Actions
const formState = window.__SERVER_FORM_STATE__;
hydrateRoot(
document.getElementById('root'),
<App />,
{ formState }
);Key Features:
Hydration Process:
Common Hydration Issues:
// ❌ Problem: Mismatch between server and client
function Component() {
// Date.now() or Math.random() will differ between server and client
return <div>{Date.now()}</div>;
}
// ✅ Solution: Use useEffect for client-only content
function Component() {
const [date, setDate] = useState(null);
useEffect(() => {
setDate(Date.now());
}, []);
return <div>{date ?? 'Loading...'}</div>;
}
// ✅ Solution: Suppress hydration warning for known mismatches
function Component() {
return <div suppressHydrationWarning>{Date.now()}</div>;
}Best Practices:
useEffect or useState for client-only dataonRecoverableError to track hydration issues<Suspense> boundariesNotes:
<Suspense> for progressive/selective hydration of deferred contentBoth createRoot and hydrateRoot return a Root object with these methods:
/**
* Render or update React elements in the root
* @param children - React elements to render
*/
root.render(children: ReactNode): void;Usage:
const root = createRoot(document.getElementById('root'));
// Initial render
root.render(<App />);
// Update render
root.render(<App newProp="value" />);
// Render different content
root.render(<DifferentApp />);
// Remove content (alternative to unmount)
root.render(null);Notes:
null removes all content but keeps the root mounted/**
* Unmount the root and cleanup all resources
*/
root.unmount(): void;Usage:
const root = createRoot(document.getElementById('root'));
root.render(<App />);
// Cleanup
root.unmount();
// Container is now empty and all event handlers are removedNotes:
React DOM provides multiple error handlers for different scenarios:
Called when an error escapes all error boundaries and reaches the root.
createRoot(container, {
onUncaughtError(error, errorInfo) {
// Error that crashed the entire app
console.error('Fatal error:', error);
reportToErrorService(error, errorInfo.componentStack);
}
});Called when an error is caught by an error boundary.
createRoot(container, {
onCaughtError(error, errorInfo) {
// Error caught by error boundary
console.log('Caught error:', error);
console.log('Boundary:', errorInfo.errorBoundary);
logNonFatalError(error);
}
});Called when React automatically recovers from an error (e.g., hydration mismatch).
createRoot(container, {
onRecoverableError(error, errorInfo) {
// React recovered but you should still log it
console.warn('Recovered from:', error);
logRecoverableError(error, errorInfo.componentStack);
}
});Use identifierPrefix when rendering multiple React roots on the same page to avoid ID collisions:
const headerRoot = createRoot(document.getElementById('header'), {
identifierPrefix: 'header-'
});
const mainRoot = createRoot(document.getElementById('main'), {
identifierPrefix: 'main-'
});
// IDs from useId will be prefixed: 'header-:r1:', 'main-:r1:', etc.createRoot enables concurrent features that improve responsiveness:
import { useState, useTransition } from 'react';
function SearchResults() {
const [query, setQuery] = useState('');
const [isPending, startTransition] = useTransition();
function handleChange(e) {
// Urgent: show what was typed
setQuery(e.target.value);
// Non-urgent: update search results
startTransition(() => {
setResults(searchResults(e.target.value));
});
}
return <input value={query} onChange={handleChange} />;
}const version: string; // "19.2.0"All client rendering APIs export the React DOM version string.
type ReactNode =
| ReactElement
| string
| number
| boolean
| null
| undefined
| ReactNode[];interface TransitionTracingCallbacks {
onTransitionStart?: (transitionName: string, startTime: number) => void;
onTransitionProgress?: (
transitionName: string,
startTime: number,
currentTime: number,
pending: Array<{ name: string }>
) => void;
onTransitionComplete?: (
transitionName: string,
startTime: number,
endTime: number
) => void;
onMarkerProgress?: (
transitionName: string,
marker: string,
startTime: number,
currentTime: number,
pending: Array<{ name: string }>
) => void;
onMarkerComplete?: (
transitionName: string,
marker: string,
startTime: number,
endTime: number
) => void;
}Note: These callbacks are unstable and primarily used by React DevTools for transition profiling.