React 18 is here! What's new?

React 18 is here! What's new?

·

5 min read

React Core Team released an alpha version of React18 recently. This release is more focused on User Experience and internal architecture changes, including adaptation to concurrent features.

We can install React 18 right away using:

npm install react@alpha

And ReactDOM,

npm install react-dom@alpha

What's New?

1. The New Root API :

We usually create a Root level DOM level like his and append the React App. This has now been deprecated and is now called "Legacy Root API"

import React from 'react';
import ReactDOM from 'react-dom';

const container = document.getElementById('root') 

ReactDOM.render(<App />, container);

Instead, a new Root API is introduced in React18, which looks like this :

import React from 'react';
import ReactDOM from 'react-dom';
import App from 'App'

const container = document.getEleementById('root')

const root = ReactDOM.createRoot(container)

root.render(<App />)

React18 will ship with both Legacy Root API and the New Root API to maintain a smooth transition of React 17(or older) apps to React 18.

Using New Root API over Legacy Root API :

There are quite a few improvements :

a.)Easy to use hydrate function as we can pass an optional boolean value directly to the root.

Legacy Root API :

import ReactDOM from 'react-dom'
import App from 'App'

const container = document.getElementById('app');


ReactDOM.hydrate(<App />, container)

New Root API :

import ReactDOM from ‘react-dom’;
import App from 'App';

const container = document.getElementById('root');


const root = ReactDOM.createRoot(container, { hydrate: true });

root.render(<App />);

Read more about hydration here

b.) Improvements in render callback :

In Legacy Root API, we could pass a render callback function. This is an anonymous function that renders/runs after the root component has been mounted.

import React from 'react';
import ReactDOM from 'react-dom';
import App from 'App'

const container = document.getElementById('root')

ReactDOM.render(<App />, container, function(){
    console.log('render only after initial component rendering')
})

console.log('render at very last')

This approach has been changed in New Root API, instead of using callbacks, React Team suggests using requestIdleCallback or even native setTimeout

2. startTransition API :

This is a new API introduced with this release, which helps in keeping the current webpage responsive and being able to do heavy non-blocking UI updates at the same time.

One important use case for startTransition could be when a user starts typing in a search box. The input value has to be immediately updated while the search results could wait few milliseconds(as expected by the user).

This API provides a way to differentiate between quick updates and delayed updates. The delayed update(i.e. transition of one UI view to another) is termed as Transition Updates.

For urgent updates like typing, hover, clicking, we call props/functions usually like this :

setText(input)

For non-urgent or heavy UI updates, we can wrap it in a startTransition API as :

startTransition(() => {

  setText(input);
});

3. Strict Effects coming to Strict Mode :

React18 will ship <StrictMode /> along with Strict Effects Mode now. Just like Strict Mode, this would be for development builds and improved DX.

When a component is wrapped in Strict Effects, React will make sure to "intentionally" run side-effects twice to detect unusual behaviour/pattern, which usually a pain point when working with useEffect mounting and cleanup functions.

Running effects twice is somewhat like, mount -> unmount -> mount

4. SSR Improvements :

Server-Side Rendering is getting an architectural overhaul in this release including improvements on first loading screen time. In the trivial version(till React 17), SSR had to load the entire page before it can start hydrating page.

This changes in React18, now we can break React components into smaller chunks using <Suspense />.

This is now called selective hydration. suppose we have 4 - 5 different components on the screen, wrapping a component in now will start hydrating the very specific component once the code has been loaded and it doesn't block the rest of the page. By having this strategy, more important parts/components of the page could become interactive first (under extreme slow connection ) while other components will continue to hydrate giving a good user experience.

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

Here, the <Delayed /> component won't be resolved until the data is fetched, till then the component will fall back to <LoadingSpinner />.

We can use <Suspense /> for several components fetching data at different times keeping important components interactive.

5. Suspense List :

Another React 18 concurrent feature, which "orchestrates" the order in which heavy data fetched components appear on the screen.

A <SuspenseList /> takes in revealOrder prop with values forward, backward or together

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

Here the card component will be revealed in a forward direction(until the data is fetched, will fell back to LoadingSpinner Component). Similarly, backwards will reveal Cards in reverse order, and together prop will render everything "together"

6. useDeferredValue :

useDeferredValue takes in a state value, a timeout in milliseconds and returns a "deferred version" of that value. This value lags by the provided timeout seconds.

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

This could be a use case for a text input field. The text input would be immediately rendered to the screen however the <CardLists /> text props takes in a useDeferredValue and returns a defferedText which lags by 3 seconds. This results in delaying the Card Lists component while still allowing users to have the text field feel snappy.

function App() {
  const [text, setText] = useState("");
  const deferredText = useDeferredValue(text, { timeoutMs: 2000 }); 

  return (
    <div className="App">
    <input value={text} onChange={handleChange} />

      <CardLists text={deferredText} />
    </div>
  );
 }

Wrapping Up

React18 has been mostly about concurrent features rather than a full-blown concurrent mode (which has been hyped a lot from React16) reason being the application and libraries author can have a smooth transition and not any breaking changes.

React18 is an alpha release right now and not suitable for production shipping so APIs might evolve until it reaches a stable release by the end of this year(expected). This concludes our post about React18.

Some Important Resources that I have collected over time:

  1. https://chan.dev/posts/concurrent-mode-is-dead/
  2. https://dev.to/cassidoo/react-18-alpha-is-out-now-what-2apj
  3. https://github.com/reactwg/react-18/discussions/4
  4. https://github.com/reactwg/react-18/discussions/37

Loved this post? Have a suggestion or just want to say hi? Reach out to me on Twitter

Originally written by Abhinav Anshul for JavaScript Works