What Is New in React 18.0
React 18 introduces a foundational update focused on concurrency, alongside new features and APIs that enable more responsive applications. The changes are designed to be adopted gradually, with most existing code running without modification.
| Category | Key Changes |
|---|---|
| New Feature | Concurrent React, Automatic Batching, New Suspense Features for SSR |
| New APIs | createRoot, hydrateRoot, startTransition, useTransition, useDeferredValue, useId, useSyncExternalStore, useInsertionEffect |
| Improvements | Stricter Strict Mode behaviors, better performance for Suspense |
| Deprecated | ReactDOM.render and ReactDOM.hydrate are replaced by new root APIs. |
What is Concurrent React and why does it matter?
Concurrent React is a new behind-the-scenes mechanism that allows React to prepare multiple versions of your UI at the same time. Think of it like an app being able to work on multiple tasks without blocking the main thread.
This matters because it unlocks the potential for more fluid user interactions. It lets React interrupt a long-running render to handle a more urgent update, like a keystroke, then return to the previous task. In practice, this means your app feels more responsive under load.
You opt into concurrency features using new APIs like startTransition or useTransition. Your existing components don't need to change to benefit from the underlying architecture.
How does automatic batching reduce re-renders?
React 18 batches more state updates automatically, grouping them into a single re-render for better performance. Previously, updates inside promises, timeouts, or native event handlers weren't batched.
Now, all updates are batched by default, regardless of where they originate. This reduces unnecessary render cycles. If you ever need to opt out for immediate DOM access, you can use flushSync, but that's rarely needed.
What new hooks help with transition updates?
The useTransition hook and startTransition API let you mark certain state updates as non-urgent "transitions." This keeps the UI responsive during the update.
For example, typing in a search field can be marked as urgent, while filtering the results can be marked as a transition. React will interrupt the slow filter render if a new keystroke comes in. useDeferredValue is a related hook for deferring re-rendering of a non-urgent part of the tree.
const [isPending, startTransition] = useTransition();
// Urgent: Show typed text immediately
setInputValue(input);
// Non-urgent: Mark filtering as a transition
startTransition(() => {
setFilter(input);
});
How is Server-Side Rendering improved with Suspense?
React 18 introduces architectural improvements to SSR powered by Suspense. The server can now stream HTML, sending pieces of the component tree as they become ready.
This allows the client to start hydrating earlier, before the entire page content is received. It also supports <Suspense> boundaries on the server, so slow parts of your app don't block the initial HTML from being sent. The new hydrateRoot API is required to use these features.
What are the new Strict Mode behaviors?
Strict Mode in React 18 simulates future React features by intentionally double-invoking certain functions. It now unmounts and remounts components in development to surface bugs related to improper cleanup in effects.
This helps ensure your components are resilient to effects mounting twice, which may be a default behavior in future React versions. It's a development-only check, but fixing the issues it uncovers makes your code more robust.
FAQ
Do I need to rewrite my app for React 18?
No. Updating to React 18 requires minimal changes for most apps. The main step is switching from ReactDOM.render to the new createRoot API. Concurrent features are opt-in, so you can adopt them incrementally.
What's the difference between `createRoot` and the old `render` method?createRoot creates a root that enables all React 18 features, including concurrent rendering. The old ReactDOM.render API still works but runs in a legacy mode without the new features and logs a warning.
When should I use `useTransition` vs `useDeferredValue`?
Use useTransition when you have control over the state update function (e.g., inside an event handler). Use useDeferredValue when you receive a value from elsewhere (e.g., a prop) and want to defer updating a part of the UI that depends on it.
Is the new `useId` hook for generating database keys?
No. useId generates unique IDs that are stable across the server and client, which is crucial for SSR hydration. It's meant for accessibility attributes (like `htmlFor` and `id`) or other DOM identifiers, not for keys in a data list.
Why is my component rendering twice in development?
This is likely due to the new Strict Mode behavior in React 18. It double-mounts components (mount, unmount, mount again) in development to help you find bugs in your effect cleanup logic. It does not happen in production.