Skip to main content Skip to footer

What to Expect in React 18

React is one of the most popular open-source declarative JavaScript libraries. This is because of its speed, scalability, and simplicity. Its first-ever version came out in 2011, and the current stable version is 17, with version 18 on the horizon.

React 18 comes packed with new features like concurrent rendering. As explained in The Plan for React 18, the React team uses the opt-in mechanism for gradual adoption.

Because concurrency is optional, there aren’t any major changes to component behavior. This means upgrading takes much less effort than usual, as you won’t need to make large changes to your code. In fact, you might not need to make any changes at all.

At the time of writing, the React 18 release timeline is as follows:

  • Alpha release: available (not recommended for production applications)
  • Public beta: several months after the alpha release
  • Release candidate (RC): several weeks after the beta release
  • General availability: several weeks after the RC version release

React releases Alpha updates regularly as npm packages with the @alpha tag. To experiment with the React 18 alpha version, we can install it using these commands:

  • React: npm install react@alpha
  • React-DOM: npm install react-dom@alpha

As we eagerly await React 18’s release, let’s explore its new features.

GrapeCity Provides Premium JavaScript Solutions

Including SpreadJS and Wijmo

Check them out today!

New Root API

In React 17, we create the React root:

import React from 'react';  
import ReactDOM from 'react-dom';  
const container = document.getElementById('root')  
ReactDOM.render(<App />, container);

From React 18 onward, that method will be called the Legacy Root API. React 18 introduces a new Root API that looks like this:

import React from 'react';  
import ReactDOM from 'react-dom';  
import App from 'App';  
const container = document.getElementById('root')  
const root = ReactDOM.createRoot(container)  
root.render(<App />)

React 18 ships with the Legacy Root API and the new Root API to allow gradual opt-in to the new, improved Root API.

The root is the top-level data structure pointer attached to the DOM element, which was opaque before version 18. In the legacy root API, we could pass a render callback function to render after the root component mounts, like this:

//Pre version 17 code  
const container = document.getElementById('root');  
ReactDOM.render(<App />, container, function(){  
    console.log('render only after initial component rendering')  
})  
console.log('render at very last');

In version 18, we first call the createRoot function, which then calls the render method. This more manageable API enables new features. The new API eradicates the render callback argument due to difficulties timing it. Instead, the React team suggests using requestIdleCallback or even native setTimeout. We can use the hydrate function easily by passing the optional Boolean value to the root API directly.

// Pre-React 18  
ReactDOM.hydrate(<App />, container);  
// React 18 and onwards  
const root = ReactDOM.createRoot(container, { hydrate: true });  
root.render(<App />);

React developers created a working group where you can learn more and discuss the changes.

startTransition API

React introduces the new startTransition API with the concurrent rendering. It keeps the current webpage responsive and enables heavy, non-blocking user interface (UI) updates. Consider an example of a textbox where users can search by typing — the UI re-renders with every keystroke. We use the startTransition API to keep the UI responsive while the application fetches data.

The API differentiates between quick updates and delayed updates — that is, transition updates (transition from one UI view to another).

We generally use the startTransition API as follows:

startTransition(() => {  
  setText(input);  
});

The startTransition API aids developers with concurrency control. React marks any component updates wrapped in the startTransition API as non-urgent and interrupts them if more urgent updates occur, like clicks or key-presses. This API helps keep the UI unblocked and responsive while React handles non-urgent background work, like fetching data, updating the state, and more.

Consider a component with a text input field for searching. We need to update the text field and search through the records the instant the user presses any key. We have two functions to control the state:

  • setInputValue for updating the input field
  • searchResults for the actual search call

Before version 18, React called both functions simultaneously and multiple times. If the search call’s time was longer, the UI hung after every keystroke until the search operation finished.

Starting in React 18, when we wrap the search function in startTransition, the search query defers until the user stops inputting values. The input text and value update instantly, and the UI remains responsive without waiting for the non-urgent search operation to complete.
Wrapping the component in a startTransition has two main benefits:

  • The UI remains responsive at all times.
  • It eliminates unnecessary data-heavy calls for input that is no longer relevant.

We generally couple startTransition with the useTransition API. useTransition swaps the loading component for a spinner, further increasing UI smoothness. For example, if the search call takes a long time to complete and render, the user sees a spinner, knowing the UI is working and not hung.

We use startTransition as follows:

import { useTransition } from'react';  
const [isPending, startTransition] = useTransition();  
const callback = () => {   
     setInputValue(input);   
     startTransition(() => {  
         searchResults(input);  });  
     }  
{isPending && <Spinner />}

This GitHub thread takes a much deeper look at the transition APIs.

Strict Mode with Strict Effects

The Strict Effects Mode will ship with <StrictMode/>. When we wrap a component in strict effects, React will intentionally run side-effects twice to detect unusual and unsafe behavior, by running multiple mounting and cleanup functions. This step ensures more resilient components and also correct behavior with fast refresh during mount and unmount cycles.

The forthcoming Offscreen API will enable better performance by hiding components instead of unmounting them. This step ensures that React maintains the state and calls the mount and unmounts effects.

This working group thread takes a deeper dive into Strict Effects.

Concurrent Rendering

React 18 introduces Concurrent Rendering, which is the new name for Concurrent Mode. The name change is essential for enabling developers to adopt concurrent features and ensure seamless code rewrites gradually.

Server-Side Rendering (SSR)

In React 18, server-side rendering gets an architectural overhaul, including first loading screen time improvements. This new SSR architecture solves three significant challenges:

  • We no longer wait to load all the data on the server before sending the HTML. We can start rendering the HTML as soon as we have enough data to render the application and then fill in or stream the rest of the HTML when it is ready.
  • Hydration can start even before the JavaScript code loads. React preserves the HTML and hydrates the HTML as the associated code loads.
  • Hydration need not be complete before we can interact with the user interface. The new SSR relies on Selective Hydration to prioritize components the user interacts with for a smoother UI experience.

Selective Hydration
Selective Hydration, mentioned above, breaks bigger React components into smaller ones using <Suspense/>.

For example, imagine our page has two or three different components. If one part started hydrating or loading in the earlier versions, that would block the rest of the UI.

With the new SSR and Suspense component, hydrating a specific component will not block the rest of the UI, providing an overall jitter-free user experience. We use Suspense like this:

<Layout>  
  <Suspense fallback={<LoadingSpinner />}>  
    <TimeConsumingComponent />  
  <Suspense />  
<Layout />

React won't resolve the <TimeConsumingComponent /> component until it can fetch the data, and the component will fall back to <LoadingSpinner />.

We can use <Suspense /> when several components fetch data. This keeps essential elements interactive. You can delve deeper into the complete changes to Suspense in this thread.

Streaming HTML

For Streaming HTML using the new SSR, we need to use the new pipeToNodeWritable method instead of the old renderToString method.

The basic syntax of the pipeToNodeWritable API is:

pipeToNodeWritable(React.Node, Writable, Options): Controls

The pipeToNodeWriteable API is not yet available since it is a work in progress, but it will have the following features:

  • Full built-in <Suspense/> support for in-parallel data fetching.
  • Code splitting with lazy, which ensures that the already-rendered components do not disappear while re-rendering.
  • Streaming HTML with delayed content blocks rendering later.

There is a detailed discussion in this GitHub thread.

Suspense List

The Suspense List is another concurrent rendering feature that helps orchestrate hiding and unhiding the heavy data-fetching components on the screen. We wrap the components in <SuspenseList />. It takes in the revealOrder property with values forward, backward, or together. For example:

<SuspenseList revealOrder="forwards">  
  <Suspense fallback={<LoadingSpinner />}>  
    <SomeComponent id={1} />  
  </Suspense>  
  <Suspense fallback={<LoadingSpinner />}>  
    <SomeComponent id={2} />  
  </Suspense>  
 </SuspenseList>

Using the code above, React reveals the component "SomeComponent" in a forward direction. That is, the component will fall back on the LoadingSpinner until it fetches data. With the backward option, React reveals the components in the reverse order. And, using together, it displays all the elements together.

useDeferredValue

The useDeferredValue is a React hook, and it takes in a state value and a timeout in milliseconds, then returns a "deferred version" of that value. It causes the value to lag by, at the most, the timeoutMs. The syntax is:

const deferredValue = useDeferredValue(value, { timeoutMs: 3000 });

Consider a component called <BookLists /> with a text input. We use the deferred value with a timeout of 5 sec, which causes the BookLists components to render with a lag of five seconds, but the text input remains responsive and updated with the current values. So, the overall user experience is jitter-free.

Code:

function App() {  
  const [text, setText] = useState("");  
  const deferredText = useDeferredValue(text, { timeoutMs: 4000 });  
  return (  
    <div className="App">  
    <input value={text} onChange={handleChange} />  
      <BookLists text={deferredText} />  
    </div>  
  );  
 }

Automatic Batching

React batches under the hood, and developers don't need to worry about it. However, it's always a good idea to understand the inner workings of a framework or language.
React collects all the setStates which change variables inside functions and execute them together. These steps are called batching. Batching is excellent for performance because it avoids unnecessary re-renders and half-finished states, which can cause UI glitches and bugs.

However, before React 18, React didn’t batch consistently. It only performed batching during browser events like a key-press or click. The batching instead happened after the event had passed, that is, a fetch callback.

With the new Root API, React automatically batches all states, no matter their origin. So, it handles all the updates inside timeouts and promises the same way as React events. This process helps React better its performance by cutting out unnecessary rendering.

Consider the code below:

function App() {  
  const [count, setCount] = useState(0);  
  const [flag, setFlag] = useState(false);  
  function handleClick() {  
    setCount(c => c + 1);  
    setFlag(f => !f);  
  }  
  return (  
    <div>  
      <button onClick={handleClick}>Next</button>  
      <h1 style=\{{ color: flag ? "blue" : "black" }}>{count}</h1>  
    </div>  
  );  
}

Before React 18, the handleClick function didn’t perform batching until the end of the function. From React 18 onward, all functions within the handleClick function will cause batching.

This GitHub thread explains automatic batching in detail.

Next Steps

In summary, React 18 introduces the following main features:

  • New Root API
  • New SSR architecture with Suspense, Suspense List, and Selective Hydration
  • Automatic batching
  • Better concurrency control with the new Transition API

The React development team provides the React 18 Roadmap, React 18 Working Group, and React 18 Discussions, so you can participate and keep abreast with the latest developments in React 18.

When you upgrade to React 18 or continue using React 17, you can slash your development time with ready-made React components like Wijmo and SpreadJS.

Using these tools, you can quickly create feature-rich interactive UI components like dashboards, spreadsheets, grids, and more. These flexible, scalable components are security compliant and integrate well with React and other frameworks. Enhance your application's user experience with minimal effort.

GrapeCity Provides Premium JavaScript Solutions

Including SpreadJS and Wijmo

Check them out today!

Joel Parks - Product Manager

Joel Parks

Technical Engagement Engineer
comments powered by Disqus