Context
Basic Example
You can also use the Class.contextType or Context.Consumer API, let us know if you have trouble with that.
Extended Example
Using React.createContext
with an empty object as default value.
Using React.createContext
and context getters to make a createCtx
with no defaultValue
, yet no need to check for undefined
:
Notice the explicit type arguments which we need because we don't have a default string
value:
along with the non-null assertion to tell TypeScript that currentUser
is definitely going to be there:
This is unfortunate because we know that later in our app, a Provider
is going to fill in the context.
There are a few solutions for this:
You can get around this by asserting non null:
const currentUserContext = React.createContext<string>(undefined!);(Playground here) This is a quick and easy fix, but this loses type-safety, and if you forget to supply a value to the Provider, you will get an error.
We can write a helper function called
createCtx
that guards against accessing aContext
whose value wasn't provided. By doing this, API instead, we never have to provide a default and never have to check forundefined
:import * as React from "react";/*** A helper to create a Context and Provider with no upfront default value, and* without having to check for undefined all the time.*/function createCtx<A extends {} | null>() {const ctx = React.createContext<A | undefined>(undefined);function useCtx() {const c = React.useContext(ctx);if (c === undefined)throw new Error("useCtx must be inside a Provider with a value");return c;}return [useCtx, ctx.Provider] as const; // 'as const' makes TypeScript infer a tuple}// Usage:// We still have to specify a type, but no default!export const [useCurrentUserName, CurrentUserProvider] = createCtx<string>();function EnthusasticGreeting() {const currentUser = useCurrentUserName();return <div>HELLO {currentUser.toUpperCase()}!</div>;}function App() {return (<CurrentUserProvider value="Anders"><EnthusasticGreeting /></CurrentUserProvider>);}You can go even further and combine this idea using
React.createContext
and context getters./*** A helper to create a Context and Provider with no upfront default value, and* without having to check for undefined all the time.*/function createCtx<A extends {} | null>() {const ctx = React.createContext<A | undefined>(undefined);function useCtx() {const c = React.useContext(ctx);if (c === undefined)throw new Error("useCtx must be inside a Provider with a value");return c;}return [useCtx, ctx.Provider] as const; // 'as const' makes TypeScript infer a tuple}// usageexport const [useCtx, SettingProvider] = createCtx<string>(); // specify type, but no need to specify value upfront!export function App() {const key = useCustomHook("key"); // get a value from a hook, must be in a componentreturn (<SettingProvider value={key}><Component /></SettingProvider>);}export function Component() {const key = useCtx(); // can still use without null check!return <div>{key}</div>;}Using
React.createContext
anduseContext
to make acreateCtx
withunstated
-like context setters:export function createCtx<A>(defaultValue: A) {type UpdateType = React.Dispatch<React.SetStateAction<typeof defaultValue>>;const defaultUpdate: UpdateType = () => defaultValue;const ctx = React.createContext({state: defaultValue,update: defaultUpdate,});function Provider(props: React.PropsWithChildren<{}>) {const [state, update] = React.useState(defaultValue);return <ctx.Provider value={{ state, update }} {...props} />;}return [ctx, Provider] as const; // alternatively, [typeof ctx, typeof Provider]}// usageconst [ctx, TextProvider] = createCtx("someText");export const TextContext = ctx;export function App() {return (<TextProvider><Component /></TextProvider>);}export function Component() {const { state, update } = React.useContext(TextContext);return (<label>{state}<input type="text" onChange={(e) => update(e.target.value)} /></label>);}A useReducer-based version may also be helpful.
Mutable Context Using a Class component wrapper
Contributed by: @jpavon