Using React Context in a TypeScript App

Recently I had the opportunity to use the new React 16 library which comes with features such as the Context API and Error Boundaries. This article covers my experience using the Context API in a TypeScript app.

🚩 From React’s official documentation:

Context provides a way to pass data through the component tree without having to pass props down manually at every level.

This saves the programmer from having to pass these properties down through components which might not even need to use these properties. In my app, I needed to globally share data that resembled application settings but was loaded from an API.

To use the new Context API, first ensure that you have the latest version of React and its type files. The new version contains several bug fixes and improvements for the Context API.

Creating the Context

Creating a Context is easy.

The createContext create a Context object with a default value. The Context object requires one type parameters which TypeScript will automatically infer from the defaultValue provided.

A more useful case would be where the defaultValue is a non-trivial type such as an object. Let’s define an interface for our context’s data.

Unfortunately, TypeScript’s compiler will complain here as providing a defaultValue is compulsory. I find this requirement of Context API absurd because in many cases, a meaningful default value can not be provided.

We can workaround this limitation by changing the context’s type parameters a bit.

Notice how we had to pass in null as a default value.

We should now add the correct exports for your provider and consumer (which we explore next) as well as the data interface.

Pro-tip: Rename your provider and consumer to have unique names before exporting them. This makes it easier to auto-import them in VS Code.

Providing the Context

Somewhere in your App, you will create a context provider which will pass down the value you provide to all the descendant consumers.

In this example, I’ve passed a constant as a value to the AppContextProvider but this could easily have been a value loaded from an API or stored in the component state.

Consuming the Context

Now we create a context consumer that will receive the context value as long as it is a descendant of a context provider.

The AppContextConsumer subscribes to context changes. In this example, we’re creating a div that contains the Name, Author and Url values which are retrieved from the context and updated whenever the context value changes.

AppContextConsumer requires a function as a child. This function receives the context value and returns a JSX element.

In our case, the appContext has the type AppContextInterface | null so we must check for null or else the TypeScript compiler will complain if we try to use appContext. This is why I used {appContext => appContext && …} here.

Pro-tip: Using an HOC (which I cover next) instead of the Context consumer component results in more readable code.

Higher-Order Component for Consuming Context

If a lot of components depend on context, it can quickly become tedious to wrap each one in <AppContextConsumer>. Instead, we can use a Higher-Order Component (HOC):

That makes using context so easy! 🎉 Just wrapping any component that accepts an appContext property with withAppContext() makes it a subscriber to our context.

Implementing the HOC in TypeScript

Writing the HOC in TypeScript can become complicated so its worth looking at an example from React’s documentation first: this is written in JavaScript but adapted for our use-case:

Since the official React documentation does not give TypeScript examples, it took me sometime to write a properly type-checked HOC. Here it is:

Pro-tip: Bookmark this snippet for Context HOC! You’re gonna need it many times during React-TypeScript development. You can thank me later. 😏

Note that if the TypeScript compiler complains on Exclude keyword then you’re using a version of TypeScript older than 2.8 which does not support conditional types; fix this by updating your TypeScript compiler.

Complete Example in Action 🏃

Checkout the complete example I’ve put together on Code Sandbox that shows all the pieces (Context, Consumer and Provider) in action.

You can also edit this example! 😃

Here’s another version which implements the same functionality but uses Higher-Order Component to consume Context:

Next Steps 👣