Context

Basic Example

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.

Thanks to @AlvSovereign

Extended Example

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:

  1. 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.

  2. We can write a helper function called createCtx that guards against accessing a Context whose value wasn't provided. By doing this, API instead, we never have to provide a default and never have to check for undefined:

    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>
    );
    }

    View in the TypeScript Playground

  3. 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>;
    }

    View in the TypeScript Playground

  4. Using React.createContext and useContext to make a createCtx with unstated-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>
    );
    }

    View in the TypeScript Playground

  5. 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;

Something to add? File an issue.

Last updated on by krishnaUIDev