I want to make React table custom component with row selection in single, multiple and none selection mode. And since is refactoring process of current oversized table component I want to make it as simple as possible in case of setup and props.
I tried some attempts with infer
and unions like this:
type SimpleTableSelectionOptionsSingle = {
selected: string | null;
onChange: (selected: string | null) => void;
};
type SimpleTableSelectionOptionsMultiple = {
selected: string[];
onChange: (selected: string[]) => void;
};
type SimpleTableSelectionOptions<T extends string | string[] | null> = T extends string
? SimpleTableSelectionOptionsSingle
: T extends string[]
? SimpleTableSelectionOptionsMultiple
: never;
type Props = {
//...other props
selectionOptions?: SimpleTableSelectionOptions<???>
}
But I'm ended up providing some default generics types to SimpleTableSelectionOptions. I wanted to specify type between string
| string[]
| null
by provided data. And kinda obvious that null
and string
should be specified in pair, just like string[]
and []
- empty string array (but here's no type required). I'm asking here, because I don't even know if it's possible to achieve props like:
<SimpleTable
//...other props
selectionOptions={{
selected: 'some-id', // Since this is string
onChange: (selected) => { /* something... */ }, // selected here also needs to be string
}}
/>
AND in the same time:
<SimpleTable
//...other props
selectionOptions={{
selected: ['some-id', 'some-other-id'], // Since this is Array<string>
onChange: (selected) => { /* something... */ }, // selected here also needs to be Array<string>
}}
/>
I tried also some helper functions but they're not so handy, like normal infered types and I wanted to avoid them. I know how to handle checks and setup in javascript to make it working. Only thing that I can't handle is types definitions.
Ok, so there we go! This is handled for selections. I needed to cast them anyway.
const handleSelection = useCallback(
(row: T) => {
if (!selectionOptions) {
return;
}
const { selected, setSelected } = selectionOptions;
if (Array.isArray(selected)) {
const typedSelected = selected as string[];
const typedSetSelected = setSelected as (selected: string[]) => void;
if (typedSelected.includes(row.id)) {
typedSetSelected(typedSelected.filter((id) => id !== row.id));
} else {
typedSetSelected([...typedSelected, row.id]);
}
} else if (typeof selected === 'string') {
const typedSelected = selected as string;
const typedSetSelected = setSelected as (selected: string | null) => void;
if (typedSelected === row.id) {
typedSetSelected(null);
} else {
typedSetSelected(row.id);
}
}
},
[selectionOptions]
);
And here's typings:
interface SimpleTablePropsSelectionSingle<T extends SimpleTableDataType>
extends SimpleTablePropsBase<T> {
selectionOptions?: {
selected: string | null;
setSelected: (selected: string | null) => void;
};
}
interface SimpleTablePropsSelectionMultiple<T extends SimpleTableDataType>
extends SimpleTablePropsBase<T> {
selectionOptions?: {
selected: string[];
setSelected: (selected: string[]) => void;
};
}
export type SimpleTableProps<T extends SimpleTableDataType> =
| SimpleTablePropsSelectionSingle<T>
| SimpleTablePropsSelectionMultiple<T>;