Stefan Baumgartner

Web ops, performance and front-end

TypeScript and React: Context

React’s context API allows you to share data on a global level. To use it, you need two things:

With React’s typings, context works without you doing anything else. Everything is done using type inference and generics. You consume all the implied type information which @types/react produces for you.

Create a context

Let’s have a look! First, we create a context. The most important thing is to not forget default properties.

import React from 'react';

export const AppContext = React.createContext({ 
  authenticated: true,
  lang: 'en',
  theme: 'dark'
});

And with that, everything you need to do in terms of types is done for you. We have three properties called authenticated, lang and theme, they are of types boolean and string respectively. React’s typings take this information to provide you with the correct types when you use them.

Provide context

Our app component provides this context. It also sets values different from default values.

const App = () => {
  return <AppContext.Provider value={ {
    lang: 'de',
    authenticated: true,
    theme: 'light'
  } }>
    <Header/>
  </AppContext.Provider>
}

Now, every component inside this tree can consume this context. You already get type errors when you forget a property or use the wrong type:


const App = () => {
  // ⚡️ compile error! Missing properties
  return <AppContext.Provider value={ {
    lang: 'de', 
  } }>
    <Header/>
  </AppContext.Provider>
}

Now, let’s consume our global state.

Consume context

Consuming context is done via render props (see the previous chapter) for more details). You can destructure you render props as deep as you like, to get only the props you want to deal with:

const Header = () => {
  return <AppContext.Consumer>
  {
    ({authenticated}) => {
      if(authenticated) {
        return <h1>Logged in!</h1>
      }
      return <h1>You need to sign in</h1>
    }
  }
  </AppContext.Consumer>
}

Because we defined our properties earlier with the right types, authenticated is of type boolean at this point. Again we didn’t have to do anything to get this extra type safety.

Context without default values

The whole example above works best if we have default properties and values. Sometimes, you don’t have default values or you need to be more flexible in which properties you want to set.

Generics for createContext and the Partial helper can help you greatly with that:

import React from 'react';

// We define our type for the context properties right here
type ContextProps = { 
  authenticated: boolean,
  lang: string,
  theme: string
};

// we initialise them without default values, to make that happen, we
// apply the Partial helper type.
export const AppContext = 
  React.createContext<Partial<ContextProps>>({});

const Header = () => {
  return <AppContext.Consumer>
  {
    ({authenticated}) => {
      if(authenticated) {
        return <h1>Logged in!</h1>
      }
      return <h1>You need to sign in</h1>
    }
  }
  </AppContext.Consumer>
}

// Now, we can set only the properties we really need
const App = () => {
  return <AppContext.Provider value={ {
    authenticated: true,
  } }>
    <Header/>
  </AppContext.Provider>
}

There’s a StackBlitz you can check out.

And that’s context! I think it’s nicer to use with the hooks API, but nevertheless, it’s incredibly useful!

Comments? Shoot me a tweet!