Closed benrbray closed 4 years ago
Yeah this is beyond my TypeScript knowledge. I call forth any TypeScript gurus to help out.
How about this? It would require Match
to accept an item
parameter, making the Switch / Match pattern behave more like a real switch / case:
export declare function Switch<T>(props: {
fallback?: JSX.Element;
children: JSX.Element;
}): () => JSX.Element;
declare type MatchProps<T> = {
item: any;
when: (t:any) => t is T;
children: ((item:T) => JSX.Element);
}
export declare function Match<T>(props: MatchProps<T>): JSX.Element;
Usage:
export const Explorer = (props:IExplorerProps) => {
return (<div id="tab_explorer"><For each={props.files} fallback={<div>Empty!</div>}>
{(entry:IDirEntry)=>(
<Switch>
<Match item={entry} when={isFile}>
{(item) => (<div>{item.fileName}</div>)}
</Match>
<Match item={entry} when={isFolder}>
{(item) => (<div>{item.folderName}</div>)}
</Match>
</Switch>
)}
</For></div>);
}
I already use a similar function form for passing through the when
item. Does this actually help? All I see is the wrapper is essentially casting it to any
which lets it not complain. I'm not sure if TypeScript will be smart enough to handle this scenario with JSX. You want it to recognize due to the condition being true that the type is of a certain type. That's going to be challenging since there are a lot of intermediate wrapped functions. I never say never with TypeScript but I'm not sure how it would be able to connect the dots in this case.
Thanks for the reply! You're right that the any
is a problem. I tried some more complicated type expressions including something as ugly as
// (doesn't actually work)
type MatchProps<T extends S, W extends ((t:S)=>t is T), S=unknown> = {
item: S;
when: W;
children: (item: W extends ((t:S)=>t is Extract<infer P, S>) ? P : never) => JSX.Element;
}
Thankfully, I eventually came up with this workaround, that doesn't require any changes to SolidJS:
const admitFile = (e:IDirEntry):IFile|false => (e.type == "file") ? e : false;
const Explorer = (props:IExplorerProps) => { return (
<Switch>
<Match when={admitFile(props.entry)}>
{(item) => (<div>{item.fileName}</div>)}
</Match>
</Switch>
)};
This isn't an ideal solution, but it definitely works!
And the generic version:
function matches<S extends T, T=unknown>(e:T, predicate:((e:T) => e is S)):S|false {
return predicate(e) ? e : false;
}
const Explorer = (props:IExplorerProps) => { return (
<Switch>
<Match when={matches(props.entry, isFile)}>
{(item) => (<div>{item.fileName}</div>)}
</Match>
</Switch>
)};
Yeah your first example is where I thought this was heading which might have been hard to generalize. In any case I'm happy you figured out a solution. It isn't perfect but it fits the case. It is probably possible for yourself or someone interested to make a custom control flow with your exact desired behavior (matching for subtyping) and have the typing work but as a generic conditional I think it might be convoluted.
In any case I'm happy you found a solution, and thank you for posting it.
I'm using SolidJS with TypeScript. I'm having trouble using Switch / Match with TypeScript discriminated unions, since type predicates in the
when={}
prop ofMatch
are not remembered by the children. For example:The current type definitions for Switch / Match are:
I tried fiddling around with the types but couldn't really get it to work properly. Any idea whether this sort of thing is possible, or any ideas for a workaround?