Carl Rippon

Building SPAs

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

React Context with TypeScript: Part 4 - Creating a context with no default and no undefined check

March 10, 2020
reacttypescript

Photo by mohammad takhsh on Unsplash

This is the final post in a series of posts on React context with TypeScript. In the previous post, we consumed a context in a class component. In this post, we will learn how to create a context without having to pass a default and then do any undefined checks when consuming it.

The problem

The type for createContext requires a default value to be passed into it, but often it doesn’t make sense to pass a default. So, we end up passing undefined as the default:

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

… and then checking for undefined everywhere we consume it:

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

A quick solution

A quick solution to the problem is to use the not-null assertion operator and remove undefined from the context type.

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

This works nicely because consuming code doesn’t need undefined checks. However, we are still passing a default.

Creating a wrapper for creating a context

A solution is to create a wrapper around createContext that deals with the default and the undefined check:

export function createCtx<ContextType>() {
  const ctx = React.createContext<
    ContextType | undefined
  >(undefined);
  function useCtx() {
    const c = React.useContext(ctx);
    if (!c)
      throw new Error(
        "useCtx must be inside a Provider with a value"
      );
    return c;
  }
  return [useCtx, ctx.Provider] as const;
}

This function first creates the context with the generic type passed into it with undefined as its default value.

A nested function is then defined, which wraps the useContext hook. A variable c is assigned to the return value of the useContext hook which is the generic type passed in or undefined:

Type of c

We then throw an error if c is falsy, which deals with the undefined check. This means that when c is returned from the nested function, it can’t undefined and is only the generic type we passed in:

Type of c

Notice also we use a const assertion (as const) on the last line to ensure TypeScript infers a tuple type rather than an array of union types.

Creating a context

We can now use our createCtx function to create a context rather than React’s createContext:

const [useTheme, CtxProvider] = createCtx<
  ThemeContextType
>();

Creating a provider

Our createCtx function returns a tuple, which contains a provider component in the second element (CtxProvider). We can the create our specific provider component containing our required state:

export const ThemeProvider = ({
  children
}: Props) => {
  const [theme, setTheme] = React.useState(
    "white"
  );
  ...
  return (
    <CtxProvider value={{ theme, setTheme }}>      {children}
    </CtxProvider>  );
};

This can then be placed at the appropriate position in the component tree:

export const App = () => (
  <ThemeProvider>    <Header />
  </ThemeProvider>);

Consuming the context

Our createCtx also returns a hook (useTheme) in the tuples first element. We can use this without having to do any undefined checks:

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>
  );
};

Neat!

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

Wrap up

The createCtx function is a generic function that can be used to create contexts for many situations. It simplifies consuming code because checks for undefined are not necessary.

That concludes this series of posts on React context with TypeScript. I hope you enjoyed it!

Did you find this post useful?

Let me know by sharing it on Twitter.
Click here to share this post on Twitter

If you to learn more about using TypeScript with React, you may find my course useful:

Using TypeScript with React

Using TypeScript with React
Find out more

Want more content like this?

Subscribe to receive notifications on new blog posts and courses

Required
© Carl Rippon
Privacy Policy