// Before: only React events were batched
setTimeout(() => {
setSize((oldSize) => oldSize + 1);
setOpen((oldOpen) => !oldOpen);
// React will render twice, once for each state update (no batching)
}, 1000);
// After: updates inside of timeouts, promises,
// native event handlers or any other event are batched
setTimeout(() => {
setSize((oldSize) => oldSize + 1);
setOpen((oldOpen) => !oldOpen);
// React will only re-render once at the end (that is batching)
}, 1000);
React 17 and older
React 18
import { flushSync } from "react-dom";
// Note: we are importing from react-dom, not react
function handleSubmit() {
flushSync(() => {
setSize((oldSize) => oldSize + 1);
});
// React has updated the DOM by now
flushSync(() => {
setOpen((oldOpen) => !oldOpen);
});
// React has updated the DOM by now
}
// Before
import { render } from "react-dom";
const container = document.getElementById("app");
render(<App tab="home" />, container);
// After
import { createRoot } from "react-dom/client";
const container = document.getElementById("app");
const root = createRoot(container);
root.render(<App tab="home" />);
createRoot
// Before
import { hydrate } from "react-dom";
const container = document.getElementById("app");
hydrate(<App tab="home" />, container);
// After
import { hydrateRoot } from "react-dom/client";
const container = document.getElementById("app");
const root = hydrateRoot(container, <App tab="home" />);
// Unlike with createRoot,
// you don't need a separate root.render() call here.
hydrateRoot
What hydration is? - CSR
What hydration is? - SSR
import { startTransition } from "react";
// Urgent: Show what was typed in the input
setInputValue(newInputValue);
// Mark any state updates inside as transitions
// and mark them as non-urgent
startTransition(() => {
// Transition: Show the results
setSearchQuery(newInputValue);
});
function App() {
const [isPending, startTransition] = useTransition();
const [count, setCount] = useState(0);
function handleClick() {
startTransition(() => {
setCount((oldCount) => oldCount + 1);
});
}
return (
<div>
<span>Current count: {count}</span>
{isPending && <Spinner />}
<button onClick={handleClick}>Increment</button>
</div>
);
}
<Suspense fallback={<PageSkeleton />}>
<RightColumn>
<ProfileHeader />
</RightColumn>
<LeftColumn>
<Suspense fallback={<LeftColumnSkeleton />}>
<Comments />
<Photos />
</Suspense>
</LeftColumn>
</Suspense>
function CodeOfConductField() {
const id = useId();
return (
<>
<label htmlFor={id}>Do you agree with our Code of Conduct?</label>
<input id={id} type="checkbox" name="coc" />
</>
);
}
function SearchResults() {
const query = useSearchQuery("");
const deferredQuery = useDeferredValue(query);
// Memoizing tells React to only re-render when deferredQuery changes,
// not when query changes.
const suggestionResuls = useMemo(
() => <SearchSuggestions query={deferredQuery} />,
[deferredQuery]
);
return (
<>
<SearchInput query={query} />
<Suspense fallback="Loading suggestion results...">
{suggestionResuls}
</Suspense>
</>
);
}