create Typed Context (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>
)
}