React 18: New Features and Upgrade Guide
React 18: New Features and Upgrade Guide
React 18 introduces concurrent features that fundamentally improve how React applications render. Let's explore the new capabilities.
Installation
npm install react react-dom
Automatic Batching
React 18 automatically batches state updates, even in promises and timeouts.
Before React 18
// Only batched in React event handlers
function handleClick() {
setCount(c => c + 1);
setFlag(f => !f);
// Only one re-render
}
// NOT batched before React 18
setTimeout(() => {
setCount(c => c + 1);
setFlag(f => !f);
// Two re-renders
}, 0);
With React 18
// Now batched everywhere!
setTimeout(() => {
setCount(c => c + 1);
setFlag(f => !f);
// Only one re-render!
}, 0);
fetch('/api').then(() => {
setCount(c => c + 1);
setFlag(f => !f);
// Only one re-render!
});
Transitions
Transitions let you mark UI updates as non-urgent.
useTransition Hook
import { useTransition } from 'react';
function SearchComponent() {
const [isPending, startTransition] = useTransition();
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
const handleChange = (e) => {
// Urgent: update input immediately
setQuery(e.target.value);
// Non-urgent: can be delayed
startTransition(() => {
setResults(filterResults(e.target.value));
});
};
return (
<div>
<input value={query} onChange={handleChange} />
{isPending && <span>Loading...</span>}
<Results data={results} />
</div>
);
}
startTransition API
import { startTransition } from 'react';
// Mark updates as transitions
startTransition(() => {
setPage(nextPage);
});
Suspense for Data Fetching
Basic Suspense
import { Suspense } from 'react';
function App() {
return (
<Suspense fallback={<Loading />}>
<DataComponent />
</Suspense>
);
}
function DataComponent() {
const data = use(fetchData()); // Suspends until data is ready
return <div>{data}</div>;
}
Suspense with Transitions
function App() {
const [page, setPage] = useState('home');
const [isPending, startTransition] = useTransition();
const navigate = (newPage) => {
startTransition(() => {
setPage(newPage);
});
};
return (
<div>
<nav>
<button onClick={() => navigate('home')}>Home</button>
<button onClick={() => navigate('about')}>About</button>
</nav>
{isPending && <LoadingIndicator />}
<Suspense fallback={<PageLoading />}>
<Page name={page} />
</Suspense>
</div>
);
}
New Hooks
useId
Generate unique IDs for accessibility:
import { useId } from 'react';
function Checkbox() {
const id = useId();
return (
<>
<label htmlFor={id}>Checkbox</label>
<input id={id} type="checkbox" />
</>
);
}
useDeferredValue
Defer updating a value:
import { useDeferredValue, useMemo } from 'react';
function SearchResults({ query }) {
const deferredQuery = useDeferredValue(query);
const results = useMemo(() =>
filterLargeList(deferredQuery),
[deferredQuery]
);
return <Results data={results} />;
}
useSyncExternalStore
Subscribe to external stores:
import { useSyncExternalStore } from 'react';
function useOnlineStatus() {
return useSyncExternalStore(
(callback) => {
window.addEventListener('online', callback);
window.addEventListener('offline', callback);
return () => {
window.removeEventListener('online', callback);
window.removeEventListener('offline', callback);
};
},
() => navigator.onLine,
() => true // Server snapshot
);
}
useInsertionEffect
For CSS-in-JS libraries:
import { useInsertionEffect } from 'react';
function useStyle(rule) {
useInsertionEffect(() => {
const style = document.createElement('style');
style.textContent = rule;
document.head.appendChild(style);
return () => style.remove();
}, [rule]);
}
Strict Mode Changes
React 18's Strict Mode is stricter:
<React.StrictMode>
<App />
</React.StrictMode>
Double Invocation
Components are mounted, unmounted, and remounted:
function Component() {
useEffect(() => {
console.log('Mount');
return () => console.log('Unmount');
}, []);
return <div>Component</div>;
}
// Console output in Strict Mode:
// Mount
// Unmount
// Mount
Concurrent Rendering
Key Concepts
- Rendering is interruptible - React can pause and resume
- Updates have priority - Urgent updates interrupt non-urgent
- Background rendering - Prepare new content while showing old
Example: Priority Updates
function App() {
const [text, setText] = useState('');
const [isPending, startTransition] = useTransition();
const handleChange = (e) => {
const newText = e.target.value;
// High priority: immediate
setText(newText);
// Low priority: can be delayed
startTransition(() => {
renderExpensiveContent(newText);
});
};
return (
<div>
<input value={text} onChange={handleChange} />
<ExpensiveComponent text={text} />
</div>
);
}
Server-Side Rendering Improvements
Streaming SSR
// server.js
import { renderToPipeableStream } from 'react-dom/server';
const { pipe } = renderToPipeableStream(<App />, {
bootstrapScripts: ['/client.js'],
onShellReady() {
response.setHeader('Content-Type', 'text/html');
pipe(response);
},
});
Selective Hydration
// Components hydrate as they become visible
<Suspense fallback={<Loading />}>
<SlowComponent />
</Suspense>
Upgrade Guide
1. Update Dependencies
npm install react react-dom
2. Update Entry Point
// Before
import ReactDOM from 'react-dom';
ReactDOM.render(<App />, root);
// After
import { createRoot } from 'react-dom/client';
const root = createRoot(document.getElementById('root'));
root.render(<App />);
3. Update Tests
// @testing-library/react v13+
import { render } from '@testing-library/react';
test('renders', () => {
render(<App />);
});
Breaking Changes
- Automatic batching - May affect code expecting multiple renders
- Stricter Strict Mode - Effects run twice
- New root API -
createRootrequired for concurrent features
Opting Out of Batching
import { flushSync } from 'react-dom';
function handleClick() {
flushSync(() => {
setCount(c => c + 1);
});
// React has updated the DOM
flushSync(() => {
setFlag(f => !f);
});
// Two renders, immediate DOM updates
}
Conclusion
React 18's concurrent features enable better user experiences by prioritizing urgent updates and allowing React to prepare content in the background. Upgrade is straightforward, and most applications will benefit automatically from automatic batching.