Recently we had an exciting Lightning Talk led by Godfrey Best, who walked us through the changes introduced by React 18.
React 18 ushers in structural changes to the library that will help developers create more performant applications. Among these changes are the highly anticipated concept of concurrent rendering, which gives the developer fine-grain control over how their components render. We will discuss these changes at a (mostly) high level, in the hopes that you walk away from this article with a solid understanding of the changes that this version introduces.
What Is React?
Before we dive into React 18, we’re going to take a brief look at what React is for those who are not familiar. Feel free to skip this section if you already have a good grasp of React.
React is, succinctly, a JavaScript library for building user interfaces. It was created at Meta in 2011 (many of you will remember that it was called Facebook, at the time) and open sourced in 2013. It swiftly became the most popular frontend library/framework.
It provides a declarative, component-based API so that you don’t need to worry about page changes on every update. You pass data to the components, and React determines how they should render. React renders a Virtual DOM (not to be confused with the Shadow DOM), listens for changes in component data, and by default re-renders only those components whose data has changed.
React data is generally passed unilaterally (from parent to child, and not vice-versa), and is usually either a component property (data that is passed from a parent to a child) or a part of component state (internal data that belongs to a specific component and can only be directly changed by that same component).
For most of React’s lifespan, it has relied on synchronous rendering, which means that once an application has begun rendering, the user must wait for the render to be completed before they can interact with the components (these render methods and their callbacks are pushed to JavaScript’s single-threaded call stack).
About Version 18
React 18 was released on March 29th, 2022, and among other changes, it adds features that allow the developer to switch from synchronous rendering to asynchronous rendering, or as React has coined it, concurrent features. This allows React to render and re-render its components outside of the call stack, unblocking the user’s opportunity to interact while the render process occurs. In addition, the developer can establish priority for certain renders, giving them more granular control over their applications. React concurrent features:
- are opt-in (when upgrading to React 18, components are not automatically set to render concurrently)
- are backwards compatible
- employ reusable state
- are interruptible
How Can We Use These New Features?
There are a number of new hooks introduced in React 18, most which are expected to be implemented by framework authors, such as Next.js, Hydrogen and Remix. A few of the new hooks made available are:
- useId
- Used for generating unique ids on both the client and server to prevent hydration mismatches
- useInsertionEffect
- Allows for CSS and JavaScript libraries to address performance issues while they are injecting styles during rendering
- useSyncExternalStore
- Allows external stores to support concurrent reads by forcing updates to the stores to be synchronous (useful for state management libraries like Redux)
- useTransition
- Allows the library’s authors to mark certain actions as low priority (such as switching between pages) *We’ll be taking a closer look at this hook later in the article
React 18 also splits the rendering API (ReactDOM) into 2 parts:
- ReactDOM/client
- ReactDOM/server
What Features Can We Use Today?
Not all of these features require time for frameworks (or us) to implement; some of them can be used today, out-of-box:
Automatic Batching
Before React 18, if you had multiple state updates that were called inside of a React event handler function, they would be batched automatically, and the component would only be re-rendered once. This formerly only applied to React state handlers, and not, for example, setTimeouts or native event handlers. React 18 changes this by automatically batching all state updates inside any function by default. This reduces unnecessary re-renders.
useDeferredValue()
This tells React to only render a value when it’s convenient, similar to debounce, though this feature has superior performance to the former. Unlike debounce, there is no fixed time delay before this fires; additionally, this can be interrupted and does not block user input.
useTransition()
This hook is similar to useDeferredValue(), except that it tells React to render a state update when it’s convenient. It can also show if a transition is pending, which is a useful status to have awareness of within your application.
Suspense For Data Fetching
This feature actually existed in previous versions of React, but it was only used for code splitting. In React 18, suspense is available for data fetching, allowing for a declarative fallback ui in scenarios when the application is waiting on an asynchronous action to complete.
Conclusion
React 18 brings some long awaited performance boons and quality-of-life improvements. The move away from synchronous to concurrent rendering is something React has been working on since 2017 and now developers can finally avail themselves of its benefits. React also has some future developments in the pipeline:
- Rendering components offscreen, allowing a developer to prepare ui to render in advance of the ui being on the page
- Improvements around suspense for data fetching, such as more exposed primitives to make it easier to access your data, as well as the ability to use the feature without a framework having to implement it
- Server components (an experimental but upcoming feature), allowing developers to build apps spanning both the client and the server