Skip to content

Instantly share code, notes, and snippets.

@p7g
Last active February 19, 2021 21:11
Show Gist options
  • Save p7g/968acc1a4e645a9466a3eba6bdbbc416 to your computer and use it in GitHub Desktop.
Save p7g/968acc1a4e645a9466a3eba6bdbbc416 to your computer and use it in GitHub Desktop.
Some React context wrappers
import React from "react";
type Setter<T> = (newValueOrUpdater: T | ((oldValue: T) => T)) => void;
type FaaC<T, P = {}> = React.ComponentType<P & { children: (t: T) => (React.ReactElement | null) }>;
interface MutableContext<T> {
Consumer: FaaC<T>;
Mutator: FaaC<Setter<T>>;
Provider: React.ComponentType<{ value: T }>;
useValue: () => T;
useSetter: () => Setter<T>;
}
export function createMutableContext<T>(defaultValue: T): MutableContext<T> {
type Context = MutableContext<T>;
const context = React.createContext(defaultValue);
const setterContext = React.createContext<Setter<T>>(() => {})
function useValue() {
return React.useContext(context);
}
const Consumer: Context["Consumer"] = ({ children }) => {
return children(useValue());
};
function useSetter(): Setter<T> {
return React.useContext(setterContext);
}
const Mutator: Context["Mutator"] = ({ children }) => {
return children(useSetter());
};
const Provider: Context["Provider"] = ({ value, children }) => {
const [currentValue, setValue] = React.useState(value);
return (
<setterContext.Provider value={setValue}>
<context.Provider value={currentValue}>
{children}
</context.Provider>
</setterContext.Provider>
)
};
return {
useValue,
useSetter,
Consumer,
Mutator,
Provider,
}
}
interface ObjectContext<O extends {}> {
Consumer: <K extends keyof O>(props: { field: K, children: (value: O[K]) => (React.ReactElement | null) }) => React.ReactNode;
Provider: React.ComponentType<{ value: O }>;
useField<K extends keyof O>(field: K): O[K];
}
export function createObjectContext<O extends {}>(defaultValue: O): ObjectContext<O> {
type Context = ObjectContext<O>;
type Contexts = { [K in keyof O]: React.Context<O[K]> };
const partialContexts: Partial<Contexts> = {};
for (const key of Object.keys(defaultValue) as (keyof O)[]) {
partialContexts[key] = React.createContext(defaultValue[key]);
}
const contexts: Contexts = partialContexts as Contexts;
function useField<K extends keyof O>(field: K): O[K] {
return React.useContext(contexts[field]);
}
const Consumer: Context["Consumer"] = ({ field, children }) => {
return children(useField(field));
}
const Provider: Context["Provider"] = ({ value, children }) => {
const keys = Object.keys(contexts) as (keyof O)[];
return (
<>
{keys.reduce((acc, key) => {
const Provider = contexts[key].Provider;
return <Provider value={value[key]}>{acc}</Provider>
}, children)}
</>
);
}
return {
useField,
Consumer,
Provider,
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment