React Concurrent Mode with TypeScript

profile picture

Spencer Miskoviak

November 17, 2019

Photo by Gwen Ong on Unsplash

An experimental release of React Concurrent Mode was recently shared at React Conf with new documentation that introduces Concurrent Mode along with additional details on using suspense to fetch data and other UI patterns.

Personally, when experimenting with new things I enjoy having the guardrails of TypeScript's static type-safety. Since Concurrent Mode is still experimental, the third-party @types/react don't support the new APIs (as of writing) out-of-the-box on a fresh install.

The remainder of this post assumes you've watched or read the above links and you're now looking to start experimenting with React Concurrent Mode but with the type-safety of TypeScript!

Getting Started

The easiest way to get started with a React TypeScript app is to use create-react-app.

# Create a new React TypeScript app
npx create-react-app react-concurrent-mode-typescript-example --typescript

# Check out the new project
cd react-concurrent-mode-typescript-example

This will still create an app in "Legacy Mode". There are a few changes and additions to opt-in to Concurrent Mode. These next steps and code are based off this post on "How to Enable React Concurrent Mode".

# By default, create-react-app will use the latest stable release
yarn upgrade react@experimental react-dom@experimental

Now the proper packages are installed with the experimental features but they need to be enabled. To enable concurrent mode, the setup with react-dom needs to be changed to use the new createRoot method.

// src/index.tsx
import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";

const root = document.getElementById("root");
ReactDOM.createRoot(root).render(<App />);

However, you'll notice after doing this there is now a type error: Property 'createRoot' does not exist. This is because the types for the experimental features are not enabled by default. There are number of approaches, one option is to import the experimental types.

// src/index.tsx
import {} from "react-dom/experimental";
import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";

const root = document.getElementById("root") as HTMLElement;
ReactDOM.createRoot(root).render(<App />);

This should now fix this type error. However, there is now a new type error: Type 'null' is not assignable to type. This is because getElementById returns an HTMLElement or null and the createRoot method expects only an HTMLElement (not null). This was fixed in this example by casting the value to HTMLElement.

Another option is to instead add a reference in the already existing type definition file.

// src/react-app-env.d.ts
/// <reference types="react-scripts" />
/// <reference types="react-dom/experimental" />
/// <reference types="react/experimental" />

All the other options to opt-in to the experimental types are documented in the experimental.d.ts file.


Now, concurrent mode is enabled with the proper type definitions. The new components and APIs such as ReactDOM.createRoot or React.SuspenseList should be properly type-checked. You can avoid easy typos and instead focus on the important parts!

All of this code is available on GitHub if you'd like to explore it yourself in the skovy/react-concurrent-mode-typescript-example repository.

If you'd like to see more examples working with concurrent mode features check out the examples for the react-suspense-img package.



Practical Abstract Syntax Trees

Learn the fundamentals of abstract syntax trees, what they are, how they work, and dive into several practical use cases of abstract syntax trees to maintain a JavaScript codebase.

Check out the course