I'm building a system where:
- I have various type 'FileType' of data in my FileData table ;
- each type has a component to display its content ;
- each component has a provider function that takes a database entry, check it has the correct data type and transform it into the component argument.
Initialy, my goal was to create a mapping like this:
const componentMapping: {[T in FileType]: React.FC<any>} = {
typeA: DisplayCompA,
typeB: DisplayCompB,
typeC: DisplayCompC
}
type ProviderFunction <C extends typeof componentMapping[FileType]> = (fileData: FileData) => Promise<React.ComponentProps<C>> | React.ComponentProps<C>;
type ComponentAndProviderMapping<T extends FileType> = {type: T, ReactComponent: typeof componentMappig[T], providerFunction: ProviderFunction<typeof componentMappig[T]>}
function output = async <T extends FileType>(rec: ComponentAndProviderMapping<T>, fileData: FileData) => {
const inputData = await rec.providerFunction(fileData);
const Comp = rec.ReactComponent;
return <Comp {...inputData} />; // this is where I get an error because the expected type for inputData is an intersection of all the DisplayComp arguments, and inputData is a union of those.
}
So I took a dive in @jcalz answers, notably this one: Type narrowing of a discriminated union via mapping instead of switch
And this gitHub: https://github.com/microsoft/TypeScript/pull/47109
And I got to a code similar to this:
const DisplayA = ({argA}: {argA: number}) => {
return (<p>{argA.toString()}</p>)
}
const DisplayB = ({argB}: {argB: string}) => {
return (<p>{argB}</p>)
}
const DisplayC = ({argC}: {argC: boolean}) => {
return (<p>{argC ? "true": "false"}</p>)
}
interface FileData {someString: "abc"}
type RecordMapDisplay = {
A: typeof DisplayA,
B: typeof DisplayB,
C: typeof DisplayC
}
async function inputFunctionA(fileData: FileData) {
return {argA: fileData.someString.length}
}
async function inputFunctionB(fileData: FileData) {
return {argB: fileData.someString}
}
async function inputFunctionC(fileData: FileData) {
return {argC: fileData ? true : false}
}
type RecordMapInputFunction = {
A: {func: typeof inputFunctionA},
B: {func: typeof inputFunctionB},
C: {func: typeof inputFunctionC}
}
type GenInputParam <K extends keyof RecordMapInputFunction = keyof RecordMapInputFunction> = {
[P in K]: { type: P } & RecordMapInputFunction[P]
}[K];
const DisplayMapping: {[K in keyof RecordMapInputFunction]: React.ComponentType<Awaited<ReturnType<RecordMapInputFunction[K]["func"]>>>} = {
A: DisplayA,
B: DisplayB,
C: DisplayC
}
const output = async <K extends keyof RecordMapInputFunction>(view: GenInputParam<K>, fileData: FileData) => {
const fn = view.func;
const Comp = DisplayMapping[view.type];following JSX naming conventions
const data = await fn(fileData);
return <Comp {...data} />; // I get the error below
}
And I still get this error:
Type '{ argA: number; } | { argB: "abc"; } | { argC: boolean; }' is not assignable to type 'IntrinsicAttributes & LibraryManagedAttributes<{ A: ComponentType<{ argA: number; }>; B: ComponentType<{ argB: "abc"; }>; C: ComponentType<{ argC: boolean; }>; }[K], Awaited<...>>'.
Type '{ argA: number; }' is not assignable to type 'LibraryManagedAttributes<{ A: ComponentType<{ argA: number; }>; B: ComponentType<{ argB: "abc"; }>; C: ComponentType<{ argC: boolean; }>; }[K], Awaited<ReturnType<RecordMapInputFunction[K]["func"]>>>'.ts(2322)
So I'm clearly failing to implement the recommandation from both the SO answer and the github thread.
Can anyone help me correct my code?