Context
Basic Example
import { createContext } from "react";
interface AppContextInterface {
name: string;
author: string;
url: string;
}
const AppCtx = createContext<AppContextInterface | null>(null);
// Provider in your app
const sampleAppContext: AppContextInterface = {
name: "Using React Context in a Typescript App",
author: "thehappybug",
url: "http://www.example.com",
};
export const App = () => (
<AppCtx.Provider value={sampleAppContext}>...</AppCtx.Provider>
);
// Consume in your app
import { useContext } from "react";
export const PostInfo = () => {
const appContext = useContext(AppCtx);
return (
<div>
Name: {appContext.name}, Author: {appContext.author}, Url:{" "}
{appContext.url}
</div>
);
};
You can also use the Class.contextType or Context.Consumer API, let us know if you have trouble with that.
Extended Example
Using createContext with an empty object as default value.
interface ContextState {
// set the type of state you want to handle with context e.g.
name: string | null;
}
//set an empty object as default state
const Context = createContext({} as ContextState);
// set up context provider as you normally would in JavaScript [React Context API](https://reactjs.org/docs/context.html#api)
Using createContext and context getters to make a createCtx with no defaultValue, yet no need to check for undefined:
import { createContext, useContext } from "react";
const currentUserContext = createContext<string | undefined>(undefined);
function EnthusasticGreeting() {
const currentUser = useContext(currentUserContext);
return <div>HELLO {currentUser!.toUpperCase()}!</div>;
}
function App() {
return (
<currentUserContext.Provider value="Anders">
<EnthusasticGreeting />
</currentUserContext.Provider>
);
}
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:
(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
createCtxthat guards against accessing aContextwhose value wasn't provided. By doing this, API instead, we never have to provide a default and never have to check forundefined:
import { createContext, useContext } 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 = createContext<A | undefined>(undefined);
function useCtx() {
const c = 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>
);
}
View in the TypeScript Playground
- You can go even further and combine this idea using
createContextand context getters.
import { createContext, useContext } 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 = createContext<A | undefined>(undefined);
function useCtx() {
const c = 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
export 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 component
return (
<SettingProvider value={key}>
<Component />
</SettingProvider>
);
}
export function Component() {
const key = useCtx(); // can still use without null check!
return <div>{key}</div>;
}
View in the TypeScript Playground
- Using
createContextanduseContextto make acreateCtxwithunstated-like context setters:
import {
createContext,
Dispatch,
PropsWithChildren,
SetStateAction,
useState,
} from "react";
export function createCtx<A>(defaultValue: A) {
type UpdateType = Dispatch<SetStateAction<typeof defaultValue>>;
const defaultUpdate: UpdateType = () => defaultValue;
const ctx = createContext({
state: defaultValue,
update: defaultUpdate,
});
function Provider(props: PropsWithChildren<{}>) {
const [state, update] = useState(defaultValue);
return <ctx.Provider value={{ state, update }} {...props} />;
}
return [ctx, Provider] as const; // alternatively, [typeof ctx, typeof Provider]
}
// usage
import { useContext } from "react";
const [ctx, TextProvider] = createCtx("someText");
export const TextContext = ctx;
export function App() {
return (
<TextProvider>
<Component />
</TextProvider>
);
}
export function Component() {
const { state, update } = useContext(TextContext);
return (
<label>
{state}
<input type="text" onChange={(e) => update(e.target.value)} />
</label>
);
}
View in the TypeScript Playground
- A useReducer-based version may also be helpful.
Mutable Context Using a Class component wrapper
_Contributed by: [@jpavon](https://github.com/typescript-cheatsheets/react/pull/13)_interface ProviderState {
themeColor: string;
}
interface UpdateStateArg {
key: keyof ProviderState;
value: string;
}
interface ProviderStore {
state: ProviderState;
update: (arg: UpdateStateArg) => void;
}
const Context = createContext({} as ProviderStore); // type assertion on empty object
class Provider extends React.Component<
{ children?: ReactNode },
ProviderState
> {
public readonly state = {
themeColor: "red",
};
private update = ({ key, value }: UpdateStateArg) => {
this.setState({ [key]: value });
};
public render() {
const store: ProviderStore = {
state: this.state,
update: this.update,
};
return (
<Context.Provider value={store}>{this.props.children}</Context.Provider>
);
}
}
const Consumer = Context.Consumer;