Created
December 14, 2025 05:36
-
-
Save xantiagoma/f38f92981d47d6cfef2e9f4d5e602494 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import type { ComponentType, ReactNode } from "react"; | |
| type AnyProps = Record<string, unknown>; | |
| /** | |
| * A provider entry for `Providers`. | |
| * | |
| * This is intentionally "erased" (non-generic) so you can compose a heterogeneous list of | |
| * providers (each with different props) in one array. | |
| * | |
| * Use `provider(...)` to create a `ProviderSpec` with correct prop inference. | |
| */ | |
| export type ProviderSpec = readonly [ | |
| Provider: ComponentType<unknown>, | |
| props: AnyProps, | |
| ]; | |
| type RequiredKeys<T> = { | |
| // biome-ignore lint/complexity/noBannedTypes: Intentional optional-key detection pattern. | |
| [K in keyof T]-?: {} extends Pick<T, K> ? never : K; | |
| }[keyof T]; | |
| type ProviderInputProps<TProps extends AnyProps> = Omit<TProps, "children">; | |
| type ProviderArgProps<TProps extends AnyProps> = | |
| keyof ProviderInputProps<TProps> extends never | |
| ? Record<string, never> | |
| : ProviderInputProps<TProps>; | |
| type ProviderPropsArg<TProps extends AnyProps> = | |
| RequiredKeys<ProviderInputProps<TProps>> extends never | |
| ? [props?: ProviderArgProps<TProps>] | |
| : [props: ProviderInputProps<TProps>]; | |
| /** | |
| * Create a typed `ProviderSpec` tuple with prop inference and required-prop enforcement. | |
| * | |
| * - If a provider has required props (excluding `children`), `provider(...)` requires them. | |
| * - If a provider has no props (excluding `children`), the `props` argument is optional and | |
| * rejects extra keys. | |
| * | |
| * @example Provider with required props (must pass props) | |
| * ```tsx | |
| * function ThemeProvider(props: { theme: "light" | "dark"; children?: ReactNode }) { | |
| * return props.children; | |
| * } | |
| * | |
| * const providers = [ | |
| * provider(ThemeProvider, { theme: "dark" }), | |
| * ] as const; | |
| * ``` | |
| * | |
| * @example Provider with no props (props argument is optional) | |
| * ```tsx | |
| * function PermissionsProvider(props: { children?: ReactNode }) { | |
| * return props.children; | |
| * } | |
| * | |
| * const providers = [ | |
| * provider(PermissionsProvider), | |
| * ] as const; | |
| * | |
| * // @ts-expect-error no extra props allowed | |
| * provider(PermissionsProvider, { other: 123 }); | |
| * ``` | |
| */ | |
| export const provider = <TProps extends AnyProps>( | |
| Provider: ComponentType<TProps>, | |
| ...args: ProviderPropsArg<TProps> | |
| ): ProviderSpec => { | |
| const props = (args[0] ?? {}) as AnyProps; | |
| return [Provider as ComponentType<unknown>, props] as const; | |
| }; | |
| /** | |
| * Props for the `Providers` component. | |
| */ | |
| export type ProvidersProps = { | |
| /** | |
| * List of providers (outer → inner), created with `provider(...)`. | |
| */ | |
| providers: readonly ProviderSpec[]; | |
| children?: ReactNode; | |
| }; | |
| const getProviderKey = (providerComponent: unknown, index: number): string => { | |
| const providerName = | |
| (providerComponent as { displayName?: string }).displayName ?? | |
| (providerComponent as { name?: string }).name ?? | |
| "Provider"; | |
| return `${providerName}:${index}`; | |
| }; | |
| function ProviderComponent({ | |
| Provider, | |
| props, | |
| children, | |
| }: { | |
| Provider: ComponentType<unknown>; | |
| props: AnyProps; | |
| children: ReactNode; | |
| }): ReactNode { | |
| const Component = Provider as ComponentType< | |
| AnyProps & { children?: ReactNode } | |
| >; | |
| return <Component {...props}>{children}</Component>; | |
| } | |
| /** | |
| * Compose multiple providers without deeply nesting JSX. | |
| * | |
| * @example | |
| * ```tsx | |
| * <Providers | |
| * providers={[ | |
| * provider(PermissionsProvider), | |
| * provider(ThemeProvider, { theme: "dark" }), | |
| * ]} | |
| * > | |
| * <App /> | |
| * </Providers> | |
| * ``` | |
| */ | |
| export const Providers = ({ providers, children }: ProvidersProps): ReactNode => | |
| providers.reduceRight<ReactNode>( | |
| (acc, [Provider, props], index) => ( | |
| <ProviderComponent | |
| key={getProviderKey(Provider, index)} | |
| Provider={Provider} | |
| props={props} | |
| > | |
| {acc} | |
| </ProviderComponent> | |
| ), | |
| children ?? null | |
| ); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment