I have a function that filters some data from a server, based off of an array of options (an HTML select
sort of thing). However, in some versions of the function I have all the data from the server from the get-go, and other times I only have part of the data. When I only have partial data, I want the filter's options
array to be objects with content
(what's rendered on the screen) and optionKey
(which I send to the server for it to do the filtering). When I have the full data, I want the filter's options
array to be objects with content
, optionKey
, and a filter
function , that corresponds to a specific key of the filtered data. I scoured online and found a solution that works pretty well for me:
type HasPartialData = {
options: ({ content: string; optionKey: string })[];
};
type HasFullData<T> = {
[K in keyof T]-?: {
columnKey: K;
options: ({ content: string; optionKey: string; filter: (value: T[K], row: T) => boolean })[];
};
}[keyof T];
type FilterProps<T> = (
| ({
hasPartial: true;
} & HasPartialData)
| ({
hasPartial: false;
} & HasFullData<T>)
);
However, I've run into a problem. I have a function (FilterMap
) that receives a hasPartial
flag and an array of either HasPartialData
or HasFullData<T>
, depending on the hasPartial
flag:
type FilterMapProps<T> = (
| {
filters: HasPartialData[];
hasPartial: true;
}
| {
filters: HasFullData<T>[];
hasPartial: false;
}
);
But when I try to pass this to the Filter
function, TypeScript can't tell that they're "synced up" and that passing them should work.
My original attempt was this:
function FilterMap<T>(props: FilterMapProps<T>) {
return props.filters.map((filter) => {
return Filter({ ...filter, hasPartial: props.hasPartial });
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
});
}
But it causes this TypeScript error:
Argument of type '(HasFullData<T> & { hasPartial: boolean; }) | { hasPartial: boolean; options: { content: string; optionKey: string; }[]; }' is not assignable to parameter of type 'FilterProps<T>'.
Type '{ hasPartial: boolean; options: { content: string; optionKey: string; }[]; }' is not assignable to type 'FilterProps<T>'.
Type '{ hasPartial: boolean; options: { content: string; optionKey: string; }[]; }' is not assignable to type '{ hasPartial: true; } & HasPartialData'.
Type '{ hasPartial: boolean; options: { content: string; optionKey: string; }[]; }' is not assignable to type '{ hasPartial: true; }'.
Types of property 'hasPartial' are incompatible.
Type 'boolean' is not assignable to type 'true'.(2345)
So I tried this:
if (props.hasPartial) {
return props.filters.map((filter) => {
return Filter({ ...filter, hasPartial: props.hasPartial });
});
} else {
return props.filters.map((filter) => {
return Filter({ ...filter, hasPartial: props.hasPartial });
});
}
...which worked, but I think it's really ugly.
The only solution I felt kind of OK with was:
return props.filters.map((filter) => {
return Filter({ ...filter as any, hasPartial: props.hasPartial });
});
But I don't like this use of as any
...
Here's a full example in the TypeScript playground.
I really want a way to do this without the ugly if
statement and without as any
... Any help would be appreciated!
You could provide an extra generic parameter to capture the Partial
ness of each type:
type HasData<T, Partial extends boolean> = (
Partial extends true
? HasPartialData
: HasFullData<T>
)
type FilterProps<T, Partial extends boolean> = (
HasData<T, Partial> & { hasPartial: Partial }
);
type FilterMapProps<T, Partial extends boolean> = (
{ filters: HasData<T, Partial>[], hasPartial: Partial }
);
etc