Reputation: 770
Goal
Simplify the following situation
// Simplify
loading ? <Loading/> : (
<Fragment>
....
</Fragment>
)
// Into something like
<LoadingContent loading={loading}>
{() => (
<Fragment>
...
</Fragment>
)}
</LoadingContent>
Problem
if the loading condition is an attribute of an object, used for discriminated union, access properties based on this attribute being true will not be infered, and it'll not compile.
Current solution
Use short circuit the evaluation with logical &&, but it duplicate the condition and the aim is to make it simpler.
<LoadingContent loading={loading}>
{() => loading && (
<Fragment>
...
</Fragment>
)}
</LoadingContent>
From proposal:
Problem, generic type must be specified and can't be inferred due to https://github.com/microsoft/TypeScript/issues/16597
interface LoadingDiscriminatedContentProps<
A extends string,
S,
SS extends S,
O extends { [P in A]: S }
> {
object: O;
attribute: A;
discriminant: SS;
children: (loaded: Extract<O, O & { [P in A]: SS }>) => React.ReactElement;
}
export function LoadingDiscriminatedContent<
A extends string,
S,
SS extends S,
O extends { [P in A]: S }
>({
object,
attribute,
discriminant,
children
}: LoadingDiscriminatedContentProps<A, S, SS, O>): React.ReactElement {
if (object[attribute] === (discriminant as S)) {
return children(object as Extract<O, O & { [P in A]: SS }>);
}
return <Loading />;
}
Upvotes: 1
Views: 356
Reputation: 872
I managed to solve this. The key was that LoadingContent calls the children callback with type T & {loaded: true}
.
LoadingContent.tsx:
interface Props<L extends string, T extends { [P in L]: boolean }> {
value: T;
loadedProp: L;
children: (loaded: T & { [P in L]: true }) => React.ReactElement;
}
const LoadingContent = <L extends string, T extends { [P in L]: boolean }>({value, loadedProp, children}: Props<L, T>): React.ReactElement => {
if (value[loadedProp]) {
return children(value as any);
}
return <>Loading...</>;
}
Usage:
interface LoadedData {
loaded: true;
data: string;
}
interface LoadingData {
loaded: false;
}
type Data = LoadedData | LoadingData;
const obj: Data = {loaded: true, data: ''};
const el = (
<LoadingContent value={obj} loadedProp="loaded">
{(obj) => (
<>
{obj.data /* here we can use obj.data*/}
</>
)}
</LoadingContent>
);
If your "loaded" property is always the same, you can omit the generic L type
Upvotes: 1