How can I abstract passing repeated children properties while keeping types non-optional?
TypeScript checks to make sure that you assign all required properties to the React element. Since you assign the extra properties in the OutsideComponent
the compiler can't really check that.
One option would be to specify children as a function that takes extra properties as a parameter and spread them to InsideComponent
. The syntax is a bit more convoluted but it is more type safe:
interface OutsideComponentProps {
repeatedThing: string;
children: (outerProps: OutsideComponentProps) => React.ReactElement<any>;
}
const OutsideComponent: React.SFC<OutsideComponentProps> = (o) => o.children(o);
const Example = () => (
<OutsideComponent repeatedThing="foo">{(o) =>
<div>
<InsideComponent uniqueThing="1" {...o} />
<InsideComponent uniqueThing="2" {...o} />
<InsideComponent uniqueThing="3" {...o} />
</div>
}</OutsideComponent>
);
It seems that
OutsideComponent
is very abstract and thus reusable; is there any way to convert it into a very generic component that takes all of its props and provides them as the argument, without having to define anOutsideComponentProps
for each case?
While you can use generic functions as components, you can't specify type parameters explicitly, they can only be inferred. This is a drawback, but ultimately it can be worked around.
function GenericOutsideComponent<T>(props: { children: (o: T) => React.ReactElement<any> } & Partial<T>, context?: any): React.ReactElement<any> {
return props.children(props as any);
}
const Example = () => (
<GenericOutsideComponent repeatedThing="foo">{(o: InsideComponentProps) =>
<div>
<InsideComponent uniqueThing="1" {...o} />
<InsideComponent uniqueThing="2" {...o} />
<InsideComponent uniqueThing="3" {...o} />
</div>
}</GenericOutsideComponent>
);
Similar to your original JavaScript solution, there is the risk that some required properties of InsideComponent
are not specified as the properties of GenericOutsideComponent
are Partial<T>
(to allow only repeatedThing
to be specified) and o
is T
because otherwise the compiler will consider repeatedThing
is not specified and will require it on InsideComponent
. If setting a dummy value on InsideComponent
is not a problem, just change the signature of children
to (o: Partial<T>) => React.ReactElement<any>
but this is less then ideal.
Another option is to be explicit about which properties are on GenericOutsideComponent
using a Pick
:
function GenericOutsideComponent<T>(props: { children: (o: T) => React.ReactElement<any> } & T, context?: any): React.ReactElement<any> {
return props.children(props);
}
const Example = () => (
<GenericOutsideComponent repeatedThing="foo">{(o: Pick<InsideComponentProps, "repeatedThing">) =>
<div>
<InsideComponent uniqueThing="1" {...o} />
<InsideComponent uniqueThing="2" {...o} />
<InsideComponent uniqueThing="3" {...o} />
</div>
}</GenericOutsideComponent>
);