UNPKG

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