1. Introducing the new root API
A root in React refers to the top-level data structure that renders a tree. In React 18, we will have two root APIs: the legacy root API and new root API.
The legacy root API is the existing API called with the ReactDOM.render method. It will create a root running in legacy mode, which is similar to usage in React version 17. Before moving React 18 to the production, a warning will be added on the usage of the legacy root API. It will gradually enforce the usage of the new root API. The legacy root API will be deprecated in upcoming versions.
import * as ReactDOM from 'react-dom';
import App from 'App';
const container = document.getElementById(‘root’);
// Initial render.
ReactDOM.render(<App tab="home" />, container);
// During an update, React will access the container element again.
ReactDOM.render(<App tab="profile" />, container);
The new root API will be invoked with the ReactDOM.createRoot method. To use it, first, we have to create the root through the createRoot method with the root element as an argument. Then, we call the root.render method and pass the app component as the parameter. By using the new root API, we can use all the enhancements and concurrent features available in React 18.
import * as ReactDOM from 'react-dom';
import App from 'App';
// Create a root.
const root = ReactDOM.createRoot(document.getElementById('root'));
// Initial render: Render an element to the root.
root.render(<App tab="home" />);
// During an update, there's no need to access the container again since we have already defined the root instance.
root.render(<App tab="profile" />);
2. Changes in hydrate method
The hydrate method is similar to the render method. But it helps to attach event listeners to the HTML elements within the containers that are rendered by the ReactDOMServer method on the server side.
React 18 replaces this hydrate method with the hydrateRoot method.
import * as ReactDOM from 'react-dom';
import App from 'App';
const container = document.getElementById('app');
// Create and render a root with hydration.
const root = ReactDOM.hydrateRoot(container, <App tab="home" />);
// Unlike the createRoot method, you don't need a separate root.render() call here.
3. Improvement in automatic batching
The batching feature in React 18 merges multiple state updates into a consolidated single rerender for improved performance. For instance, if we update more than one state value in a simple function handler. Then, React 18 will batch these updates into a single update and rerender the app only once instead of calling the render method multiple times.
This will considerably improve the app’s overall performance. It also prevents components from rendering with incomplete state updates where only one state variable is updated, which may lead to irrelevant behavior in the app.
function App() {
//Declaring state values.
Const [count, setCount] = useState(0);
const [flag, setFlag] = useState(false);
// Change state on button click.
function handleClick() {
setCount(c => c + 1); // Does not re-render yet
setFlag(f => !f); // Does not re-render yet
// React will only re-render once at the end (that’s batching!)
}
return (
<div>
<button onClick={handleClick}>Next</button>
<h1 style={{ color: flag ? “blue” : “black” }}>{count}</h1>
</div>
);
}
React 18 will automatically batch all the state updates, irrespective of where they originate. Therefore, if any update is inside a timeout, promise, native event handler, or any other event, the React 18 version will batch it in the same way as updates inside React events. This will further improve the performance of the app and lead to fewer re-renderings of the components compared to the previous React versions.
function App() {
const [count, setCount] = useState(0);
const [flag, setFlag] = useState(false);
function handleClick() {
fetchSomething().then(() => {
// React 18 and later versions batch these state updates with single rerendering.
setCount(c => c + 1);
setFlag(f => !f);
// React will rerender once at the end (that's batching!)
});
}
return (
<div>
<button onClick={handleClick}>Next</button>
<h1 style={{ color: flag ? "blue" : "black" }}>{count}</h1>
</div>
);
}
Disable automatic batching
Sometimes, we need to immediately rerender the component after each state change. In that scenario, use the flushSync method to disable the automatic batching.
import { flushSync } from 'react-dom'; // Note: react-dom, not react
function handleClick() {
flushSync(() => {
setCounter(c => c + 1);
});
// React has updated the DOM by now.
flushSync(() => {
setFlag(f => !f);
});
// React has updated the DOM by now.
}
4. Suspense
We can accomplish server-side rendering in React by rendering all the components on the server first. Then, we have to send the results as HTML elements to the browser.
In the browser, React will load JavaScript as usual. Then it will connect the HTML elements generated from the server with the JavaScript and hydration logic.
The server-side rendering (SSR) allows us to see the page content before the JavaScript bundle loads and runs.
The suspense feature will delay the rendering of components in React. It was first introduced in React version 16.6 with certain limitations and basic support for suspense rendering.
React 18’s stable version will support the full-fledged suspense feature based on the concurrent feature, along with the following:
Delayed transition: Instructs the components to wait for the data to be resolved before proceeding with a new state transition.
Placeholder throttling: Reduces UI thrash by throttling the appearance of nested placeholder and successive placeholder components.
SuspendList: Helps suspend many components by arranging them in the order in which these components need to be revealed to the user.
Earlier, the suspense feature in React 16 and 17 was called legacy suspense. The enhanced suspense in the React 18 is called concurrent suspense.
<Suspense fallback={<Loading />}>
<ComponentThatSuspends />
<Sibling />
</Suspense>
This example is a simple explanation of how a suspense component will work in the older and new React 18 versions:
The legacy suspense will immediately mount the Sibling component to the DOM. Also, its lifecycle hooks/effects will be fired. It won’t wait for the ComponentThatSuspends to be resolved.
The concurrent suspense won’t mount the Sibling component to the DOM and its lifecycle hooks/effects will not be fired until the ComponentThatSuspends is resolved. This new enhancement will resolve all the existing issues in the legacy suspense.
Conclusion
The upcoming React 18 stable version will bring an exciting new set of features to the developer community. The main focus is on concurrency and gradual up-gradation to the newer version.