DEV Community

Cover image for The best way to initialize a React Context in TypeScript
Erik Pukinskis
Erik Pukinskis

Posted on

The best way to initialize a React Context in TypeScript

React Contexts are great—they let you neatly encapsulate behavior that cuts across multiple React components.

But there are a few gotchas when using them in TypeScript.

This article will explain how to deal with one.

TL;DR

You can't easily initialize a React Context if it has required properties. Because you won't know what those properties are when you call createContext.

My makeUninitializedContext gist solves that.

The Problem

A common pattern when using React Contexts is to pass some props into your Context Provider. For example, if your app has dark mode and a light mode, your design system provider might have a theme prop:

import { DesignSystemProvider } from "~/design-system"
import { useState, useEffect, createContext } from "react"

type DesignSystemContextValue = {
  theme: "light" | "dark"
}

const DesignSystemContext = createContext(
  /* what goes here??? */
)

function App() {
  const [theme, setTheme] = useState<"dark" | "light" | undefined>()

  useEffect(() => {
    const listener = (event: MediaQueryListEvent) => {
      setTheme(event.matches ? "dark" : "light")
    }

    const query = window.matchMedia("(prefers-color-scheme: dark)")

    query.addEventListener("change", listener)

    return () => query.removeEventListener("change", listener)
  }, [])

  if (!theme) return null

  return <DesignSystemProvider theme={theme} />
}
Enter fullscreen mode Exit fullscreen mode

The trouble is, how do you then initialize your context? It's easy if you can use a default value...

import { createContext } from "react"

const DesignSystemContext = createContext({
  theme: "light",
})
Enter fullscreen mode Exit fullscreen mode

... but ...

What if you don't want a default value?

What if you want to test that your hooks behave a certain way when used outside the Context Provider?

What if your Context Provider depends on data objects that have no sane default value, like a user or a workspace?

If you try to create a context with an uninitialized object, TypeScript will yell at you:

Source code showing type error: Argument of type '{}' is not assignable to parameter of type 'DesignSystemContextValue'.<br>
Property

The Solution

The way I solve this is by including a small snippet of code in almost every React project I work on, called makeUninitializedContext.

The makeUninitializedContext function returns a Proxy object that is typed with whatever type you want:

import { createContext } from "react"
import { makeUninitializedContext } from "~/helpers"

type DesignSystemContextValue = {
  theme: "dark" | "light"
}

const DesignSystemContext = createContext(
  makeUninitializedContext<DesignSystemContextValue>("Cannot use DesignSystemContext outside a DesignSystemProvider")
)
Enter fullscreen mode Exit fullscreen mode

That gives you a Context object which is properly typed...

Image description

... and if you try to use the context object without initializing it, you get a helpful error:

Screenshot of uncaught error: Cannot use DesignSystemContext outside a DesignSystemProvider: tried getting context.theme

Making the context optional

Sometimes you have a hook that can work in either of two ways: with or without the context.

For that case, I use the isInitialized function exported in that same gist. It allows you to detect whether a context is initialized before you try to use it:

import { useContext } from "react"
import { isInitialized } from "~/helpers"

export function useThemeName() {
  const context = useContext(DesignSystemContext)

  return isInitialized(context) ? context.theme : undefined
}
Enter fullscreen mode Exit fullscreen mode

That way your hook can work either inside or outside of the Context Provider. And you won't trigger an error by accessing a property on the uninitialized context object.

I hope that's helpful for someone! Follow me on Twitter for more React tips.

AWS GenAI LIVE image

Real challenges. Real solutions. Real talk.

From technical discussions to philosophical debates, AWS and AWS Partners examine the impact and evolution of gen AI.

Learn more

Top comments (0)

nextjs tutorial video

Youtube Tutorial Series 📺

So you built a Next.js app, but you need a clear view of the entire operation flow to be able to identify performance bottlenecks before you launch. But how do you get started? Get the essentials on tracing for Next.js from @nikolovlazar in this video series 👀

Watch the Youtube series