Carl Rippon

Building SPAs

Carl Rippon
BlogBooks / CoursesAbout
This site uses cookies. Click here to find out more

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

March 03, 2020
reacttypescript

Photo by mohammad takhsh on Unsplash

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.

Learn TypeScript

NEW!🎉LIMITED LAUNCH DISCOUNT

An interactive course for JavaScript developers who want to learn modern TypeScript

  • Learn to use TypeScript's amazing type system with your existing JavaScript skills to boost your productivity
  • Over 70 interactive tutorial style lessons
  • Quizzes in each chapter to reinforce knowledge
  • Covers beginner topics through to advanced
Learn TypeScript
Find out more