id | title |
---|---|
context |
Context |
import * as React from "react";
interface AppContextInterface {
name: string;
author: string;
url: string;
}
const AppCtx = React.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
export const PostInfo = () => {
const appContext = React.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.
Using React.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 = React.createContext({} as ContextState);
// set up context provider as you normally would in JavaScript [React Context API](https://reactjs.org/docs/context.html#api)
Using React.createContext
and context getters to make a createCtx
with no defaultValue
, yet no need to check for undefined
:
import * as React from "react";
const currentUserContext = React.createContext<string | undefined>(undefined);
function EnthusasticGreeting() {
const currentUser = React.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:
const currentUserContext = React.createContext<string | undefined>(undefined);
// ^^^^^^^^^^^^^^^^^^
along with the non-null assertion to tell TypeScript that currentUser
is definitely going to be there:
return <div>HELLO {currentUser!.toUpperCase()}!</div>;
// ^
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 } // 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>; }
-
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] } // usage const [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
interface ProviderState {
themeColor: string;
}
interface UpdateStateArg {
key: keyof ProviderState;
value: string;
}
interface ProviderStore {
state: ProviderState;
update: (arg: UpdateStateArg) => void;
}
const Context = React.createContext({} as ProviderStore); // type assertion on empty object
class Provider extends React.Component<{}, 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;