@recrafter/ctx
Version:
A simple wrapper around React.createContext that makes it easy to use hooks and context together
65 lines (64 loc) • 2.34 kB
JavaScript
import React from 'react';
/**
* Error thrown when trying to use a context without a provider in the component tree.
*
* @public
*/
export class MissingCtxProviderError extends Error {
constructor(displayName) {
super(`Context provider is missing. Please mount ${displayName} component above in the component tree.`);
}
}
const MISSING_CTX_PROVIDER_SYMBOL = Symbol('No context provider');
/**
* Factory function that creates a context component with associated helper methods.
*
* This function simplifies the React Context API by creating a component that:
* - Automatically provides a context value using the given hook
* - Supports both regular children and render functions
* - Includes a use() method for consuming the context
*
* @example
* ```tsx
* // Create a context for a string value
* const Ctx = createCtx(() => React.useState(''));
*
* // Use the context in components
* const TextInput = () => {
* const [value, setValue] = Ctx.use();
* return <input value={value} onChange={(e) => setValue(e.target.value)} />;
* };
*
* // Wrap components with the context provider
* const App = () => (
* <Ctx>
* <TextInput />
* </Ctx>
* );
* ```
*
* @typeParam Value - The type of value that the context will provide.
* @typeParam Props - Type of properties that can be passed to the context provider.
* @param useValue - Hook function that creates the context value from props passed to the context provider.
* @param displayName - Optional name for the context, used for debugging and error messages.
* @returns A context component
*
* @public
*/
export const createCtx = (useValue, displayName) => {
const Context = React.createContext(MISSING_CTX_PROVIDER_SYMBOL);
const Ctx = (props) => {
const value = useValue(props);
return (React.createElement(Context.Provider, { value: value }, typeof props.children === 'function' ? props.children(value) : props.children));
};
Ctx.displayName = displayName ? `Ctx(${displayName})` : 'Ctx';
Ctx.use = () => {
// eslint-disable-next-line react-hooks/rules-of-hooks
const value = React.useContext(Context);
if (value === MISSING_CTX_PROVIDER_SYMBOL) {
throw new MissingCtxProviderError(Ctx.displayName);
}
return value;
};
return Ctx;
};