createTypedContext (React)

Easily create typed React Contexts and associated hooks without needing to dupliate types, and worry about default values! Heavily inspired by VueUse's createInjectionState.

Warning

The snippet below uses React 19's use hook. If you're on a version before v19, all you need to do is replace all uses of the use hook with useContext!

typescriptjavascript
import type { ComponentType } from 'react'
import { createContext, use } from 'react'
 
export function createTypedContext<
  TContext,
  TProps extends Record<string, unknown> = Record<never, never>
>(
  useContextValue: (props: TProps) => TContext
): [
  Provider: ComponentType<TProps & { children: React.ReactNode }>,
  useContext: () => TContext,
  useOptionalContext: () => TContext | null
] {
  const Context = createContext<TContext | null>(null)
 
  function Provider(props: TProps & { children: React.ReactNode }) {
    return (
      <Context.Provider value={useContextValue(props)}>
        {props.children}
      </Context.Provider>
    )
  }
 
  function useOptionalTypedContext() {
    const context = use(Context)
    return context
  }
 
  function useTypedContext() {
    const context = use(Context)
    if (!context) {
      throw new Error('Context not found')
    }
    return context
  }
 
  return [Provider, useTypedContext, useOptionalTypedContext]
}
import { createContext, use } from 'react'
 
export function createTypedContext(useContextValue) {
  const Context = createContext(null)
 
  function Provider(props) {
    return (
      <Context.Provider value={useContextValue(props)}>
        {props.children}
      </Context.Provider>
    )
  }
 
  function useOptionalTypedContext() {
    const context = use(Context)
    return context
  }
 
  function useTypedContext() {
    const context = use(Context)
    if (!context) {
      throw new Error('Context not found')
    }
    return context
  }
 
  return [Provider, useTypedContext, useOptionalTypedContext]
}

Examples

~/lib/secret-number/context.tsx
typescriptjavascript
import { useState } from 'react'
 
export const [SecretNumberProvider, useSecretNumber] = createTypedContext(
  ({ secretNumber }: { secretNumber }) => {
    const [multiplier, setMultiplier] = useState(1)
 
    return {
      secretNumber,
      multiplier,
      setMultiplier,
      multipliedSecretNumber: secretNumber * multiplier,
    }
  }
)
import { useState } from 'react'
 
export const [SecretNumberProvider, useSecretNumber] = createTypedContext(
  ({ secretNumber }) => {
    const [multiplier, setMultiplier] = useState(1)
 
    return {
      secretNumber,
      multiplier,
      setMultiplier,
      multipliedSecretNumber: secretNumber * multiplier,
    }
  }
)
~/component/that/renders/provider
typescriptjavascript
import { ReactNode } from 'react'
 
export function ProviderProvider(props: { children: ReactNode }) {
  return (
    <SecretNumberProvider secretNumber={42}>
      {props.children}
    </SecretNumberProvider>
  )
}
export function ProviderProvider(props) {
  return (
    <SecretNumberProvider secretNumber={42}>
      {props.children}
    </SecretNumberProvider>
  )
}
~/component/that/consumes/context
typescriptjavascript
export function ContextUser() {
  const { secretNumber, setMultiplier, multipliedSecretNumber } =
    useSecretNumber()
 
  return (
    <div>
      <div>
        <s>Secret</s> number: {secretNumber}
      </div>
      <div>
        Multiplied <s>secret</s> number: {multipliedSecretNumber}
      </div>
      <button onClick={() => setMultiplier(m => m + 1)}>MULTIPLY!!</button>
    </div>
  )
}
export function ContextUser() {
  const { secretNumber, setMultiplier, multipliedSecretNumber } =
    useSecretNumber()
 
  return (
    <div>
      <div>
        <s>Secret</s> number: {secretNumber}
      </div>
      <div>
        Multiplied <s>secret</s> number: {multipliedSecretNumber}
      </div>
      <button onClick={() => setMultiplier(m => m + 1)}>MULTIPLY!!</button>
    </div>
  )
}
Created 08/09/24Updated 09/09/24
Found a mistake, or want to suggest an improvement? Source on GitHub here
and see edit history here