Carl Rippon

Building SPAs

Carl Rippon
BlogBooksAbout
This site uses cookies. Click here to find out more

React Context with TypeScript: Part 2 - Complex context with function components

February 25, 2020
reacttypescript

Photo by mohammad takhsh on Unsplash

This is the second post in a series of posts on React context with TypeScript. In the last post, we created a simple context where TypeScript was able to infer its type.

In this post, we will implement a more complex context where we need to specify the context type explicitly.

Explicitly setting the context type

We are going to enhance the context from the last post so that the theme can be updated by consumers.

In the last post, the type for the context was inferred from the default value, which was a simple string. The type for our enhanced context is going to be a little more complex:

type ThemeContextType = {
  theme: string;
  setTheme: (value: string) => void;
};

So, there will be a theme property containing the current value for the theme and a setTheme method to update the current theme.

Reacts createContext function expects us to supply an argument for initial context value. We can supply a default value for the theme property, but it doesn’t make sense to provide a default implementation for the setTheme method. So, a simple approach is to pass in undefined as the initial value:

const ThemeContext = React.createContext(
  undefined
);

Let’s see what the inferred type is for the context value:

Inferred context type

The type of the context value is inferred to be undefined if in strict mode or any if not.

So, ThemeContext isn’t typed as we require at the moment. How can we explicitly specify the type for the context when using createContext? Well, createContext is a generic function. So, we can pass in the type for the context value as a generic parameter:

const context = React.createContext<ContextType>(...)

Therefore, we can type our context is as follows:

const ThemeContext = React.createContext<
  ThemeContextType | undefined
>(undefined);

Using the enhanced context in the provider

The modification to the context provider, from the last post, is to the value we provide from it. Instead of a simple string, it is now an object containing the theme property and the setTheme method:

export const ThemeProvider = ({
  children
}: Props) => {
  ...

  return (
    <ThemeContext.Provider value={{ theme, setTheme }}>
      {children}
    </ThemeContext.Provider>
  );
};

Adding an option to change the theme in the Header component

The useTheme custom hook remains the same as in the last post. The App component remains the same as well.

We are going to change the Header component though so that the user can change the theme:

const Header = () => {
  const { theme, setTheme } = useTheme()!;
  return (
    <div style={{ backgroundColor: theme }}>
      <select value={theme} onChange={e => setTheme(e.currentTarget.value)}>
        <option value="white">White</option>
        <option value="lightblue">Blue</option>
        <option value="lightgreen">Green</option>
      </select>
      <span>Hello!</span>
    </div>
  );
};

We destructure the current theme value as well as the setTheme method from the useTheme hook. Notice that we have put an exclamation mark (!) after the call to the useTheme hook to tell the TypeScript compiler that its return value won’t be undefined.

We have also added a drop down that has options to change the theme to White, Blue, and Green. The value of the drop down is set to the current theme value, and when this is changed, it calls the contexts setTheme method to update this in the shared state.

The full implementation of the context is available by clicking the link below. Give it a try and change the theme value and see the background change color.

Open full implementation

Wrap up

Generally, we will need to create a type for a context and explicitly define the type when the context is created. Often the initial value for the context is undefined, so the context type is usually a union type that contains undefined. We need to use the non-null assertion operator (!) when referencing the context to tell TypeScript that it does have a value.

In the 4th post in this series, we will cover a way of not having to pass a default value to the context and not having to deal with it possibly being undefined. Before this, in the next post, we will learn how to consume a strongly-typed context with class components.


Want to learn more about React and TypeScript? Check out my book
Learn React with TypeScript 3