1. Concurrent Rendering
Concurrent rendering is one of the major features in React 18. It allows React to prepare multiple versions of the UI simultaneously. With concurrent rendering, React can interrupt and resume tasks, giving priority to more urgent updates. This makes your app feel more responsive even when there are heavy computations happening in the background.
Code Example:
import { createRoot } from 'react-dom/client';
const container = document.getElementById('app');
const root = createRoot(container); // This enables concurrent rendering
root.render();
To enable concurrent rendering, you no longer use ReactDOM.render()
, but createRoot()
instead.
2. Automatic Batching
React 18 improves how updates are batched. In previous versions, only updates inside React event handlers were batched. With automatic batching, updates are batched by default, even in asynchronous situations like setTimeouts, promises, and fetches. This reduces unnecessary re-renders and boosts performance.
Code Example:
import { useState } from 'react';
function MyComponent() {
const [count, setCount] = useState(0);
const [text, setText] = useState('');
function handleClick() {
setTimeout(() => {
setCount(c => c + 1);
setText('Hello');
// Both updates will be batched automatically
}, 1000);
}
return (
<div>
<p>Count: {count}</p>
<p>Text: {text}</p>
<button onClick={handleClick}>Click Me</button>
</div>
);
}
3. The New useId
Hook
React 18 introduces a new hook called useId
, which is useful for generating unique IDs in server-rendered apps. It helps avoid potential mismatches between client and server when rendering dynamic content.
Code Example:
import { useId } from 'react';
function MyForm() {
const id = useId();
return (
<form>
<label htmlFor={id}>Name:</label>
<input id={id} type="text" />
</form>
);
}
The useId
hook generates unique IDs for elements, making it particularly useful for accessibility and form elements in both client and server-rendered applications.
4. The startTransition
API
Another addition to React 18 is the startTransition
API. This API allows you to mark non-urgent updates as “transitions,” improving the user experience by making the UI feel more responsive. Urgent updates like input changes can take priority, while other updates, like rendering large lists, can be deferred.
Code Example:
import { useState, startTransition } from 'react';
function MyApp() {
const [value, setValue] = useState('');
const [list, setList] = useState([]);
function handleChange(e) {
setValue(e.target.value);
startTransition(() => {
const newList = Array(10000).fill(e.target.value);
setList(newList); // This will be deferred to avoid blocking urgent updates
});
}
return (
<div>
<input value={value} onChange={handleChange} />
<ul>
{list.map((item, index) => (
<li key={index}>{item}</li>
))}
</ul>
</div>
);
}
5. Server-Side Rendering with Suspense
React 18 brings full support for Suspense on the server, allowing you to load components asynchronously during server-side rendering. Suspense can now handle loading states, deferring content, and fetching data before sending HTML to the client.
Code Example:
import { Suspense } from 'react';
function MyComponent() {
return (
<Suspense fallback="Loading...">
<SomeAsyncComponent />
</Suspense>
);
}
With Suspense, you can display fallback content (like loading indicators) while waiting for asynchronous data or components to be ready.
Conclusion
React 18 brings exciting features like concurrent rendering, automatic batching, the useId
hook, the startTransition
API, and server-side Suspense. These improvements focus on enhancing performance and developer experience, making React apps more efficient and responsive.
As always, make sure to thoroughly test your app before upgrading, especially for more complex applications, to ensure a smooth transition to React 18.