Last updated: Apr 29, 2026

React 18

React 18 shipped in March 2022. The headline change is concurrent rendering — a behind-the-scenes mechanism that lets React prepare multiple versions of the UI at the same time. Rendering becomes interruptible: React can pause, resume, or abandon work to keep the interface responsive. All other v18 features build on top of this foundation.

New Root API

ReactDOM.render is replaced by createRoot (and ReactDOM.hydrate by hydrateRoot). The new API is required for any concurrent feature to work.

import { createRoot } from "react-dom/client";
import App from "./App";

const root = createRoot(document.getElementById("root")!);
root.render(<App />);

Existing apps can upgrade to the new root with no other changes — concurrent features are opt-in.

Automatic Batching

In React 17, state updates were only batched inside React event handlers. React 18 batches updates everywhere — inside setTimeout, Promise callbacks, native event listeners, and any other context. Fewer re-renders happen by default.

setTimeout(() => {
  setCount((c) => c + 1);
  setFlag((f) => !f);
  // React 17: two re-renders
  // React 18: one re-render (batched)
}, 1000);

Transitions

useTransition and startTransition mark state updates as non-urgent. Urgent updates (typing, clicking) can interrupt a transition in progress so the UI never feels sluggish.

import { useState, useTransition } from "react";

function Search() {
  const [query, setQuery] = useState("");
  const [results, setResults] = useState<string[]>([]);
  const [isPending, startTransition] = useTransition();

  function handleChange(e: React.ChangeEvent<HTMLInputElement>) {
    setQuery(e.target.value); // urgent — update input immediately

    startTransition(() => {
      setResults(filterLargeList(e.target.value)); // non-urgent
    });
  }

  return (
    <>
      <input value={query} onChange={handleChange} />
      {isPending ? <p>Loading…</p> : <ResultList items={results} />}
    </>
  );
}

New Hooks

HookPurpose
useTransitionMark updates as non-urgent; returns [isPending, startTransition]
useDeferredValueDefer re-rendering a value until higher-priority updates finish
useIdGenerate a stable unique ID safe for SSR hydration
useSyncExternalStoreSubscribe to external stores with tearing protection under concurrency
useInsertionEffectInject styles before layout effects run; designed for CSS-in-JS libs

Streaming SSR with Suspense

renderToPipeableStream (Node) and renderToReadableStream (Web Streams) replace renderToString for server rendering. They stream HTML progressively and support <Suspense> boundaries on the server — slow sections show a fallback first and swap in when ready, without blocking the rest of the page.

Strict Mode Changes

In development, React 18 Strict Mode mounts every component twice (mount → unmount → mount) to surface side-effects that break with reusable state. This helps prepare for future features where React may unmount and remount parts of the tree to preserve state.