Skip to content

Instantly share code, notes, and snippets.

@xantiagoma
Created December 14, 2025 05:36
Show Gist options
  • Select an option

  • Save xantiagoma/f38f92981d47d6cfef2e9f4d5e602494 to your computer and use it in GitHub Desktop.

Select an option

Save xantiagoma/f38f92981d47d6cfef2e9f4d5e602494 to your computer and use it in GitHub Desktop.
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