Upgrading to the React 18 RC is fast and easy—start taking advantage of these great new features now to be ready for final React 18 release in a few weeks!
Very exciting news for everyone in the React community: The React 18 RC (Release Candidate) was released March 8, 2022! That means that the features are all finalized, and we only have a few weeks until the official release of React 18.
For those who have been following along with this new version, you know this has been a long time coming—React 17 was released in October 2020 (famously known as the “No Feature” release). The first React 18 Alpha release came to us in June 2021, followed by a Beta in November 2021.
We’re in the final stretch now, so it’s the perfect time to catch up on all the exciting the new stuff coming your way and start thinking about what React 18 can offer for your app! Don’t worry, though—I’ve been watching the React Conf videos, keeping an eye on the React Working Group discussions and reading all the blogs so you don’t have to. With that being said, let’s take a look at all the major takeaways you should know about React 18!
If you’ve read any of my writing before, then you know that one of the things I’m most passionate about is helping to bridge the gap between designers and developers. For that reason, I was especially excited to see React engineer Andrew Clark call out during the React 18 Keynote at React Conf 2021 all the ways in which React—and especially the new React 18 features—are based on design principles.
Screenshot from React 18 Keynote slides
For the creation of React 18, the team consulted not only with developers, but also designers and UX specialists—and I truly believe that you can see that in the new feature set we’ve been presented with as part of this RC. To quote Andrew in the keynote: “With React, designers and developers speak the same language.”
With such a strong UI focus, React has always attracted design-oriented teams and developers—it’s a huge part of why I love it so much! It’s great to see the team really leaning into that, acknowledging it in their keynote, and actively working with designers and other UI/UX professionals to further develop and improve the library.
If I were to pick one word to sum up the entire React 18 release, it would definitely be concurrency. Concurrency is a behind-the-scenes capability that powers many of the features coming in this update, like Suspense and the new startTransition()
and useDeferredValue()
APIs.
At a high level, concurrency basically means that tasks can overlap. Rather than one state update having to fully complete before the system can move on to the next one, concurrency allows us to bounce back and forth between multiples. It should be noted that this doesn’t mean those things are all happening at the same time — rather, it’s that one task can now be paused while other, more urgent tasks are seen to. Then, once the more urgent tasks are done, we can jump back to the less urgent task, bringing with us the updated information from the more urgent ones.
What React 18 is offering us (that is so cool), are the tools to work with and manipulate that concurrency flow. Developers now have more control over rendering prioritization and order than we’ve ever had before.
One of the great things about React is how human-readable the code is. It’s fairly easy for a developer to open a file and read the code, top to bottom, to quickly understand what’s happening in that component.
However, when we need to fetch and handle data, some of that ease kind of slips away. Developers often turn to data-fetching libraries, like Apollo or React Query, which provide APIs and hooks that let them skip the complexities.
Even with those solutions, though, there were still other problems to deal with—mainly, the way in which the data and the loading state were intrinsically linked. Before, we had to specify some kind of loading state and then write corresponding JSX to conditionally render based on that. That means that our UI elements were always tied to the load state of specific pieces of data.
const [loading, setLoading] = useState(true);
if myData != null {
setLoading(true);
}
<>
{ !loading &&
<MyComponent />
}
{ loading &&
<Loading />
}
<>
Suspense solves that problem by allowing us to designate fallbacks for UI elements that aren’t ready to be displayed.
<Suspense fallback={<Loading/>}>
<MyComponent myData={myData}/>
</Suspense>
What’s interesting about this is the way in which it was inspired by design principles—specifically, the concept of the skeleton layout, where the UI elements are always in place and populated when the content is ready. This approach helps the developer by allowing them to write code that more accurately resembles the actual design, closing that gap between prototype and functioning app.
This approach makes it easier to rework the UI of our pages—what loads together vs. separately, when and where—because we can just add new <Suspense>
components (even nested within other <Suspense>
components!) or move other elements into or out of existing <Suspense>
components to quickly rearrange the page layout. Because the <Suspense>
components themselves aren’t inherently tied to a specific piece of data (the way we used to do it), it separates the UI code from the functional code in a way that really prioritizes the design experience.
We’re not limited to using Suspense just for data, though—we can also use it for streaming server rendering.
Server rendering is a technique where you render the HTML output of your React component, and then send that over to the client before the JS is ready, so that the user isn’t stuck staring at a completely blank page. Before React 18, this happened in an all-or-nothing way—when all the components were ready, the page would update and the user could start interacting with the application. That meant that if you had just one really slow component, like a complex data grid, that one component could create a bottleneck.
Image from React 18 for App Developers slides
Now, though, we have Suspense! And in the same way that we talked about before, we can wrap a single slow component in those <Suspense>
tags and tell React to delay that component loading and instead focus on sending down the other, smaller ones first. You can also, as mentioned before, set a fallback to show a loading animation.
Image from React 18 for App Developers slides
This allows the user to see the content on the page as soon as it’s available, on a component-by-component basis, instead of having to wait for everything to be ready and then getting the entire thing at once. You can show the initial HTML right away, and then stream the rest!
Another great new upgrade coming to us in React 18 is automatic batching. Let’s start by talking about what batching is, before we get into the change that React 18 brings to it.
Previously, batching happened when you had multiple state updates within a single event handler; in that situation, React would only re-render once at the end of the function—not every time the state is changed. However, this wouldn’t happen outside of event handlers—if there were multiple state updates within a fetch call, for example, then the code would re-render for each one.
fetch('http://example.com/data.json').then(() => {
setIsLoading(false);
setData(data);
setError(null);
});
// Previously this code would cause 3 different re-renders, once for each state update.
// Now, these three updates will be batched together into 1 re-render.
Now, updates are batched automatically, regardless of what they’re wrapped by. This makes your code a lot more efficient, and prevents unnecessary re-rendering. However, if needed, you can opt out for specific use cases where you want the re-renders to happen.
When we use the startTransition
API, what we’re doing is marking some our less-urgent actions as “transitions” and then telling React to let other, more urgent actions take priority in the rendering timeline.
This is such an awesome update from a UX standpoint. It’s going make things feel so much snappier and more responsive for the user, as well as reducing the work that we, as developers, were putting in, in order to minimize that pain point. By wrapping those slower, less urgent updates in startTransition
, we can basically tell React that it’s fine to just get to those when it’s not busy with something more important.
That means that transitions can be interrupted by more pressing updates, and React will just throw out the unfinished, now-outdated rendering work and jump right to the new stuff. It also means that we won’t ever be in a situation where we’re losing time to a component that’s rendering outdated and inaccurate data. Or, even worse, where a user is shown information that’s no longer correct.
onChange = (e) => {
const value = e.target.value;
startTransition(() => {
nonUrgentAction(value);
});
};
Since your whole page will no longer be locked up waiting on these long processes, your user might not even realize anything is still loading!
For this reason, it’s also recommended to use the isPending
value that will also be shipping with React 18 as part of the useTransition
hook. This hook returns the startTransition
function, as well as an isPending
value which will be set to true
while your transition is rendering. That way, you can do a quick check of isPending
to determine whether you need to adjust your UI to reflect the fact that the update isn’t quite ready yet—for example, disabling a button.
const [isPending, startTransition] = useTransition();
<Button className={isPending ? 'disabled' : 'active'} />
The new useDeferredValue()
API allows us to select specific parts of our UI and intentionally defer updating them so they don’t slow down other parts of our page. There are two nice things about this:
As mentioned above, this is such a nice design-oriented update. There’s nothing worse than a page full of loading animations, and there are lots of times when slightly old data is better than no data at all. This allows our components to never feel like they’re loading, even when they really are in the background. To the user, it will just ... update! How lovely.
Here’s an example of how it might be used: Let’s assume that we’re fetching value
from a data source that updates on a regular basis, but it’s a lot of content and normally would take some time to load. Now, with useDeferredValue
, we can allow the new data to be fetched in the background and create the illusion of a quick and smooth update by having our component use the old content of value
, for up to 4000ms.
const deferredValue = useDeferredValue(value, { timeoutMs: 4000 });
return (
<div>
<MyComponent value={deferredValue} />
</div>
);
One thing to take note of is that, with React 18, we’re seeing the end of the ReactDOM.render
syntax previously used for hooking your application to the DOM. It’s being replaced with ReactDOM.createRoot
, which is necessary for support of the new features.
You can upgrade without changing ReactDOM.render
and your code will still work, but you’ll get an error in your console and you won’t be able to make use of any of the cool new stuff in this new release.
// The old way:
ReactDOM.render(
<App />,
document.getElementById('root')
);
// The new way:
ReactDOM.createRoot(document.getElementById('root'));
Root.render(<App/>);
If you’ve had your ear to the ground on past React updates, you might have previously heard the words “Concurrent Mode” tossed around. It’s important to know that this is now outdated—Concurrent Mode is no longer the adoption strategy used by React 18. Instead, you’ll hear about “concurrent features.” Or, as the React team likes to say, “There is no Concurrent Mode, only concurrent features!”
What this means, in practice, is that there’s no high-level flag or toggle that needs to be “on” in order to make use of concurrent rendering—you can just add in concurrent features wherever you need them, on a case-by-case basis, without having to worry about the impact to the rest of your application. Because all the new concurrent features are opt-in—meaning you have to go out of your way to declare an action as a transition by wrapping it in setTransition
, for example vs. anything being set automatically—your existing code won’t be affected by these changes.
React 18 will still handle all updates as urgent by default, unless you make use of any of the concurrent features to tell it otherwise. That means you can upgrade and selectively start working the new features into your codebase when you’re ready and where it makes sense!
So, what’s stopping you? Upgrading to the React 18 RC is fast and easy, so you can start taking advantage of all these great new features in your application. Get a head start on it now, and you can be ready for the final React 18 release in just a few weeks!
Kathryn Grayson Nanz is a developer advocate at Progress with a passion for React, UI and design and sharing with the community. She started her career as a graphic designer and was told by her Creative Director to never let anyone find out she could code because she’d be stuck doing it forever. She ignored his warning and has never been happier. You can find her writing, blogging, streaming and tweeting about React, design, UI and more. You can find her at @kathryngrayson on Twitter.