DEV Community

Cover image for React Context with TypeScript: Part 3 - Context with class components
Carl
Carl

Posted on • Edited on • Originally published at carlrippon.com

React Context with TypeScript: Part 3 - Context with class components

This is another post in a series of posts on React context with TypeScript. In the previous post, we created a complex context and consumed it in a function component.

In this post, we will learn about using React context with class components.

Trying to use the context hook in a class component

We are going to continue using the context we created in the last post that allows consumers to share and set a theme. Let's update the Header component to be a class component:

class Header extends React.Component {
  render() {
    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>
    );
  }
}

There is a problem with this implementation, though:

Context hooks error

Hooks can only be called inside function components, so, the above code will error on the following line:

const { theme, setTheme } = useTheme()!;

Using the context property

React class components have a context property that we can use to consume a context. First, we need to tell the class what context it should use with a static contextType property, and then we can access the context property:

class Header extends React.Component {
  static contextType = ThemeContext;

  render() {
    const { theme, setTheme } = this.context!;

    return (
      ...
    );
  }
}

Notice that we put an exclamation mark(!) after the context property to tell the TypeScript compiler that it isn't undefined.

Let's see what types theme and setTheme been inferred as:

Context inferred type in class

Both theme and setTheme has been inferred to have the any type.

Explicitly setting the type for the context property

At the moment, the consumed context isn't strongly-typed. We can explicitly define the classes context property with a type annotation to make it strongly-typed:

class Header extends React.Component {
  static contextType = ThemeContext;
  context: React.ContextType<typeof ThemeContext>;

  render() {
    const { theme, setTheme } = this.context!;

    return (
      ...
    );
  }
}

Notice that we don't use React.ContextType<ThemeContextType> as the type annotation for the context property because we get a type error if we do so.

A full working implementation 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

Using the Consumer component

There is an alternative approach to consuming a context in a class component if we just need access to it in the JSX. This method is to use the contexts Consumer component:

class Header extends React.Component {
  render() {
    return (
      <ThemeContext.Consumer>
        {value => (
          <div style={{ backgroundColor: value!.theme }}>
            <select
              value={value!.theme}
              onChange={e => value!.setTheme(e.currentTarget.value)}
            >
              <option value="white">White</option>
              <option value="lightblue">Blue</option>
              <option value="lightgreen">Green</option>
            </select>
            <span>Hello!</span>
          </div>
        )}
      </ThemeContext.Consumer>
    );
  }
}

The child of the Consumer component is a function that has the value of the context passed into it and returns the JSX we want to render. Notice that we have put an exclamation mark (!) after we reference value to tell the TypeScript compiler that this isn't undefined.

The benefit of this approach is that the contextType static property doesn't need to be implemented. We don't need to declare the context property with its type annotation as well.

Let's check the inferred type of the value parameter in the Consumer components child function:

Consumer value inferred type

The type of the value parameter is ThemeContextType | undefined

Wrap up

We can use Reacts context in class components, but we can't use the useContext hook.

Using the Consumer component is a neat way of getting access to the context in the render method, which has its type inferred correctly.

The context property can be used in other lifecycle methods to get access to the context. We need to explicitly define a type annotation for the context property and specify the specific context in a contextType static property.

In the next post, we'll learn about an approach to creating a context without having to pass a default and then do any undefined checks when consuming it.

Originally published at https://www.carlrippon.com/react-context-with-typescript-p3/ on Mar 03, 2020.

Top comments (0)