required-react-context
Version:
A simple React Context wrapper that throws an error if it is used without being provided
1 lines • 9.75 kB
Source Map (JSON)
{"version":3,"sources":["../src/index.tsx","../src/types.ts","../src/util.ts"],"sourcesContent":["import type { PropsWithChildren, ConsumerProps } from \"react\";\r\nimport React, { createContext, useContext, useDebugValue } from \"react\";\r\nimport type { Names, NamedContext, NamedContextUtils } from \"./types\";\r\nimport { UNSET_VALUE } from \"./types\";\r\nimport { assert, capitalise } from \"./util\";\r\n\r\nexport { UNSET_VALUE };\r\n\r\nexport const notSet = (caller: string, providerName: string) =>\r\n `${caller}: context value is not set. Use ${providerName} to set the value.`;\r\n\r\nconst makeNamedContext = <T,>(\r\n defaultValue: T,\r\n {\r\n providerName,\r\n contextName,\r\n }: Required<Pick<Names, \"contextName\" | \"providerName\">>,\r\n): NamedContext<T> =>\r\n Object.assign(createContext<T>(defaultValue), {\r\n providerName,\r\n displayName: contextName,\r\n });\r\n\r\nconst validateNames = (names: Names, caller: string) => {\r\n for (const [name, value] of Object.entries(names)) {\r\n assert(\r\n typeof value === \"string\",\r\n `${caller}: Expected ${name} to be a string. Got: ${typeof value}`,\r\n );\r\n }\r\n if (names.hookName) {\r\n assert(\r\n names.hookName.startsWith(\"use\"),\r\n `${caller}: hookName must start with \"use\". Got: ${names.hookName}`,\r\n );\r\n }\r\n};\r\n\r\nconst applyDefaultNames = ({\r\n name,\r\n contextName = `${capitalise(name)}Context`,\r\n providerName = `${capitalise(name)}Provider`,\r\n providerProp = name,\r\n consumerName = `${capitalise(name)}Consumer`,\r\n hookName = `use${capitalise(name)}`,\r\n}: Names): Required<Names> => ({\r\n name,\r\n contextName,\r\n providerName,\r\n providerProp,\r\n consumerName,\r\n hookName,\r\n});\r\n\r\nexport function createRequiredContext<T>(): {\r\n with: <const N extends Names>(names: N) => NamedContextUtils<T, N>;\r\n} {\r\n return {\r\n with(names) {\r\n validateNames(names, \"createRequiredContext\");\r\n\r\n const {\r\n contextName,\r\n providerName,\r\n providerProp,\r\n consumerName,\r\n hookName,\r\n } = applyDefaultNames(names);\r\n\r\n const Context = makeNamedContext<T | typeof UNSET_VALUE>(UNSET_VALUE, {\r\n providerName,\r\n contextName,\r\n });\r\n return {\r\n [contextName]: Context,\r\n [providerName](props: PropsWithChildren<Record<string, T>>) {\r\n return (\r\n <Context.Provider value={props[providerProp] as never}>\r\n {props.children}\r\n </Context.Provider>\r\n );\r\n },\r\n [consumerName](props: ConsumerProps<T>) {\r\n return (\r\n <Context.Consumer>\r\n {(value) => {\r\n assert(\r\n value !== UNSET_VALUE,\r\n notSet(consumerName, providerName),\r\n );\r\n return props.children(value);\r\n }}\r\n </Context.Consumer>\r\n );\r\n },\r\n [hookName]() {\r\n const value = useContext(Context);\r\n assert(value !== UNSET_VALUE, notSet(hookName, providerName));\r\n useDebugValue(value);\r\n return value;\r\n },\r\n } as never;\r\n },\r\n };\r\n}\r\n\r\nexport function createOptionalContext<T>(defaultValue: T): {\r\n with: <const N extends Names>(names: N) => NamedContextUtils<T, N, false>;\r\n} {\r\n return {\r\n with(names) {\r\n validateNames(names, \"createOptionalContext\");\r\n\r\n const {\r\n contextName,\r\n providerName,\r\n providerProp,\r\n consumerName,\r\n hookName,\r\n } = applyDefaultNames(names);\r\n\r\n const Context = makeNamedContext(defaultValue, {\r\n providerName,\r\n contextName,\r\n });\r\n return {\r\n [contextName]: Context,\r\n [providerName](props: PropsWithChildren<Record<string, T>>) {\r\n return (\r\n <Context.Provider value={props[providerProp] as never}>\r\n {props.children}\r\n </Context.Provider>\r\n );\r\n },\r\n [consumerName](props: ConsumerProps<T>) {\r\n return <Context.Consumer {...props} />;\r\n },\r\n [hookName]() {\r\n const value = useContext(Context);\r\n useDebugValue(value);\r\n return value;\r\n },\r\n } as never;\r\n },\r\n };\r\n}\r\n","import type { PropsWithChildren, Context, FC, ConsumerProps } from \"react\";\nimport type { Compute } from \"./util\";\n\nexport const UNSET_VALUE = Symbol.for(\"required-react-context/unset-value\");\n\nexport interface Names {\n name: string;\n contextName?: string;\n providerName?: string;\n providerProp?: string;\n consumerName?: string;\n hookName?: `use${string}`;\n}\n\ntype GetName<\n N extends Names,\n K extends keyof Names,\n Fallback extends string,\n> = N[K] extends string ? N[K] : Fallback;\n\ntype GetContextName<N extends Names> = GetName<\n N,\n \"contextName\",\n `${Capitalize<N[\"name\"]>}Context`\n>;\n\ntype GetProviderName<N extends Names> = GetName<\n N,\n \"providerName\",\n `${Capitalize<N[\"name\"]>}Provider`\n>;\n\ntype GetProviderProp<N extends Names> = GetName<N, \"providerProp\", N[\"name\"]>;\n\ntype GetConsumerName<N extends Names> = GetName<\n N,\n \"consumerName\",\n `${Capitalize<N[\"name\"]>}Consumer`\n>;\n\ntype GetHookName<N extends Names> = GetName<\n N,\n \"hookName\",\n `use${Capitalize<N[\"name\"]>}`\n>;\n\nexport interface NamedContext<T> extends Context<T> {\n displayName: string;\n providerName: string;\n}\n\nexport type NamedContextUtils<\n T,\n N extends Names,\n IsRequired extends boolean = true,\n> = Compute<\n Record<\n GetContextName<N>,\n Context<IsRequired extends true ? T | typeof UNSET_VALUE : T>\n > &\n Record<\n GetProviderName<N>,\n FC<PropsWithChildren<Record<GetProviderProp<N>, T>>>\n > &\n Record<GetConsumerName<N>, FC<ConsumerProps<T>>> &\n Record<GetHookName<N>, () => T>\n>;\n","// eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents\r\nexport type Compute<T> = { [K in keyof T]: T[K] } & unknown;\r\n\r\ntype KeyofUnion<T> = T extends any ? keyof T : never;\r\n\r\nexport type OneOf<\r\n U,\r\n K extends KeyofUnion<U> = KeyofUnion<U>,\r\n> = U extends infer T\r\n ? Compute<T & { [P in Exclude<K, keyof T>]?: undefined }>\r\n : never;\r\n\r\nexport function capitalise(str: string) {\r\n if (typeof str !== \"string\") return \"\";\r\n return str.charAt(0).toUpperCase() + str.slice(1);\r\n}\r\n\r\nexport function assert(condition: boolean, message: string): asserts condition {\r\n if (!condition) {\r\n throw new Error(message);\r\n }\r\n}\r\n\r\nexport function wait(ms: number) {\r\n return new Promise((resolve) => {\r\n setTimeout(resolve, ms);\r\n });\r\n}\r\n"],"mappings":"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","names":["src_exports","__export","UNSET_VALUE","createOptionalContext","createRequiredContext","notSet","__toCommonJS","import_react","UNSET_VALUE","capitalise","str","assert","condition","message","notSet","caller","providerName","makeNamedContext","defaultValue","contextName","validateNames","names","name","value","assert","applyDefaultNames","capitalise","providerProp","consumerName","hookName","createRequiredContext","Context","UNSET_VALUE","props","React","createOptionalContext"]}