Loading States in React Components Using TypeScript’s Discriminated Unions

Many web applications need to load data from servers that will be shown to users. It’s important for the UI to handle loading states while the requests are in-flight.

Let’s say, for example, that we have a page that will load a user’s profile. We want to be able to show a loading message, then switch to the normal UI when we get the information we need. Thankfully, we can do this in a very clean manner by using TypeScript and React together.

In this example, I’m assuming that there is some other function that wraps the React component, and it will be responsible for loading data and passing that data down to the component.

Discriminated Unions

Lately, I’ve found myself making frequent use of TypeScript’s discriminated unions. (They’re also described in the Advanced Types section of the TypeScript documentation.)

For handling the data loading in the React component, we’ll use a discriminated union to define the props the component will receive.

When I create a discriminated union type, I often start with defining an enum that will represent the discriminant. For this example, we care about what mode the page is in, so our enum will look like this:


enum UserProfilePageMode {
  LOADING = "LOADING",
  LOADED = "LOADED"
}

Props for React Component

Once we have the enum, we can go ahead and start defining some interfaces for each “mode.”


interface LoadingUserProfilePageProps {
  mode: UserProfilePageMode.LOADING;
}

interface LoadedUserProfilePageProps {
  mode: UserProfilePageMode.LOADED;
  name: string;
  email: string;
  birthday: string;
}

Now, using these interfaces, we can create our discriminated union type. This will be the type for the props that get passed into the React component.


type Props = LoadingUserProfilePageProps | LoadedUserProfilePageProps;

Rendering the Component

The last step will be using the mode property on the props to determine what to render. By utilizing a switch statement, we can handle each case that we defined with our enum.

A very simple implementation of a React component for a user profile page looks like this:


class UserProfilePage extends React.Component<Props> {
  render() {
    switch (this.props.mode) {
      case UserProfilePageMode.LOADING:
        // We won't have any other props
        return <div>Loading...</div>;
      case UserProfilePageMode.LOADED:
        // In this mode, we can display user information
        return (
          <div>
            <div>Name: {this.props.name}</div>
            <div>Email: {this.props.email}</div>
            <div>Birthday: {this.props.birthday}</div>
          </div>
        );
    }
  }
}

Thanks to TypeScript’s help, we now know exactly which properties we have access to in each case. And, as humans, we can easily read through this render function and understand that we are rendering different things based on whether or not we have the right information loaded.

This process also simplifies what we may need to return from whichever portion of the code is responsible for loading data. If it is still in the process of loading, it can simply return:


{
  mode: UserProfilePageMode.LOADING
}

Once the data we need is loaded, it can return something like this:


{
  mode: UserProfilePageMode.LOADED,
  name: "Harry Potter",
  email: "[email protected]",
  birthday: "July 31"
}

This prevents the code from having to do things like returning null or undefined portions of the props while it’s still in the loading state. Instead, we will now only need to build objects that contain the information we need.

This is a pattern I use quite frequently. It requires a little extra setup on the types, but overall, I find that it improves the readability of both the data loading functions and the render functions.