A more powerful createContext API for React
npm install @codesandbox/context
import { createContext, useContext } from "react";
const context = createContext(null as unknown as Foo);
export const useFoo = () => useContext(context);
export const FooProvider: React.FC = ({ children }) => {
return <context.Provider value="FOO">{children}</context.Provider>;
};
export const FooConsumer = context.Consumer;
In consuming components:
import { FooProvider, FooConsumer, useFoo } from "./Foo";
const Wrapper = () => {
return (
<FooProvider>
<App />
</FooProvider>
);
};
const App = () => {
const foo = useFoo();
return <FooConsumer>{(foo) => {}}</FooConsumer>;
};
import { createContext } from "@codesandbox/context";
export const useFoo = createContext(() => "FOO");
In consuming components:
import { useFoo } from "./hooks/useFoo";
const Wrapper = () => {
return (
<useFoo.Provider>
<App />
</useFoo.Provider>
);
};
const App = () => {
const foo = useFoo();
return <useFoo.Consumer>{(foo) => {}}</useFoo.Consumer>;
};
Even though @codesandbox/context
simplifies code, the value is in the conceptual simplification. A context is all about exposing a value on a context and consuming it through a hook. This API expresses exactly that with the createContext
function, taking a callback which produces the value to be exposed.
The callback passed to createContext
is executed within the returned Provider
which means you can use other hooks in it:
const useFoo = createContext(() => useState("FOO"));
This concept also simplifies exposing certain values from one context to an other, optimising reconciliation:
const useProject = createContext(() =>
useReducer(projectReducer, initialState)
);
const useProjectDispatcher = createContext(() => useProject()[1]);
Props are passed to the context callback through the provider:
const useFoo = createContext((props: { initialFoo: string }) =>
useState(props.initialFoo)
);
const SomeComponent = () => {
return <useFoo.Provider initialFoo="FOO"></useFoo.Provider>;
};
Often multiple contexts are related and you want to expose them on a single provider:
const useFoo = createContext(() => useState("FOO"));
const useBar = createContext((props: { initialBar: string }) =>
useState(props.initialBar)
);
const ComposedProvider = composeContexts({ useFoo, useBar });
// Since "useFoo" takes no props, the typing omits it
const SomeComponent = () => {
return <ComposedProvider useBar={{ initialBar: "BAR" }}></ComposedProvider>;
};