(
factoryFn: (props: P) => T,
defaults: T,
-): [provider: ContextProvider, useContext: () => T];
+): [provider: CreatedContextProvider
, useContext: () => T];
export function createContextProvider(
factoryFn: (props: P) => T,
-): [provider: ContextProvider, useContext: () => T | undefined];
+): [provider: CreatedContextProvider
, useContext: () => T | undefined];
export function createContextProvider(
factoryFn: (props: P) => T,
defaults?: T,
-): [provider: ContextProvider, useContext: () => T | undefined] {
+): [provider: CreatedContextProvider
, useContext: () => T | undefined] {
const ctx = createContext(defaults);
- return [
- props => {
- return createComponent(ctx.Provider, {
- value: factoryFn(props),
- get children() {
- return props.children;
- },
- });
- },
- () => useContext(ctx),
- ];
+ const Provider = (props => {
+ return createComponent(ctx.Provider, {
+ value: factoryFn(props),
+ get children() {
+ return props.children;
+ },
+ });
+ }) as CreatedContextProvider
;
+ Object.defineProperty(Provider, $PROVIDER_PROPS, { value: true });
+ return [Provider, () => useContext(ctx)];
}
/*
@@ -74,7 +79,7 @@ Type validation of the `values` array thanks to the amazing @otonashixav (https:
/**
* A component that allows you to provide multiple contexts at once. It will work exactly like nesting multiple providers as separate components, but it will save you from the nesting.
*
- * @param values Array of tuples of `[ContextProviderComponent, value]` or `[Context, value]` or bound `ContextProviderComponent` (that doesn't take a `value` property).
+ * @param values Array of context/value tuples, `createContextProvider` provider/props tuples, or bound provider components.
*
* @example
* ```tsx
@@ -88,21 +93,27 @@ Type validation of the `values` array thanks to the amazing @otonashixav (https:
* // after
*
*
*
* ```
*/
-export function MultiProvider(props: {
- values: {
- [K in keyof T]:
- | readonly [
- Context | ContextProviderComponent,
- [T[K]][T extends unknown ? 0 : never],
- ]
- | FlowComponent;
- };
+type MultiProviderItem = T extends readonly [infer Provider, unknown]
+ ? Provider extends CreatedContextProvider
+ ? readonly [Provider, Omit]
+ : Provider extends Context
+ ? readonly [Provider, Value]
+ : Provider extends ContextProviderComponent
+ ? readonly [Provider, Value]
+ : never
+ : T extends FlowComponent
+ ? T
+ : never;
+
+export function MultiProvider(props: {
+ values: T & { [K in keyof T]: MultiProviderItem };
children: JSX.Element;
}): JSX.Element {
const { values } = props;
@@ -111,15 +122,20 @@ export function MultiProvider(props
if (!item) return props.children;
- const ctxProps: { value?: any; children: JSX.Element } = {
+ let ctxProps: { value?: any; children: JSX.Element } = {
get children() {
return fn(i + 1);
},
};
if (Array.isArray(item)) {
- ctxProps.value = item[1];
+ const value = item[1];
item = item[0];
- if (typeof item !== "function") item = item.Provider;
+ if (typeof item === "function" && $PROVIDER_PROPS in item) {
+ ctxProps = mergeProps(value, ctxProps);
+ } else {
+ ctxProps.value = value;
+ if (typeof item !== "function") item = item.Provider;
+ }
}
return createComponent(item, ctxProps);
diff --git a/packages/context/test/index.test.tsx b/packages/context/test/index.test.tsx
index a60a16639..430d1935e 100644
--- a/packages/context/test/index.test.tsx
+++ b/packages/context/test/index.test.tsx
@@ -113,4 +113,35 @@ describe("MultiProvider", () => {
expect(capture2).toBe("World");
expect(capture3).toBe(TEST_MESSAGE);
});
+
+ test("passes arbitrary props to context providers", () => {
+ let capture;
+
+ createRoot(() => {
+
+ {untrack(() => {
+ capture = useTestContext().message;
+ return "";
+ })}
+ ;
+ });
+
+ expect(capture).toBe("Provided through MultiProvider");
+ });
+
+ test("keeps object values wrapped in the value prop for Solid context providers", () => {
+ const ObjectContext = createContext<{ message: string }>();
+ let capture;
+
+ createRoot(() => {
+
+ {untrack(() => {
+ capture = useContext(ObjectContext)?.message;
+ return "";
+ })}
+ ;
+ });
+
+ expect(capture).toBe("Object context");
+ });
});
diff --git a/packages/context/test/server.test.tsx b/packages/context/test/server.test.tsx
index 8db05a5eb..eeb8e1f1a 100644
--- a/packages/context/test/server.test.tsx
+++ b/packages/context/test/server.test.tsx
@@ -58,4 +58,38 @@ describe("MultiProvider", () => {
expect(capture2).toBe("World");
expect(capture3).toBe(TEST_MESSAGE);
});
+
+ test("passes arbitrary props to context providers", () => {
+ const [NamedProvider, useNamedContext] = createContextProvider(
+ (props: { name: string; children: JSX.Element }) => props.name,
+ );
+ let capture;
+
+ renderToString(() => (
+
+ {untrack(() => {
+ capture = useNamedContext();
+ return "";
+ })}
+
+ ));
+
+ expect(capture).toBe("Provided through MultiProvider");
+ });
+
+ test("keeps object values wrapped in the value prop for Solid context providers", () => {
+ const ObjectContext = createContext<{ message: string }>();
+ let capture;
+
+ renderToString(() => (
+
+ {untrack(() => {
+ capture = useContext(ObjectContext)?.message;
+ return "";
+ })}
+
+ ));
+
+ expect(capture).toBe("Object context");
+ });
});