Lets say I have a type that looks as follows:
export type IEntity = ({
entity: IReport
setEntity: (report: IReport) => void
} | {
entity: ITest
setEntity: (test: ITest) => void
});
Both entity and setEntity are functions from a zustand store.
My component looks like this:
type IProps = {
values: IValues
meta: IMeta
} & IEntity;
export const CreateField = (props : IProps) => {
...
So when I try to call setEntity inside this component, I get the following error:
Argument of type IReport | ITest is not assignable to parameter of type IReport & ITest
How can I fix this? How do I have to write the correct type definition??
Thanks in advance!
The compiler is unable to handle the "correlated union types" described in ms/TS#30581, however, there is a suggested refactor described in ms/TS#47109, which considers moving to generics. First, we will need some map type that will hold the part of the props that is different:
type TypeMap = {
report: IReport;
test: ITest;
};
Now, let's generate the discriminated union, but by using the generics:
type IProps<T extends keyof TypeMap> = {
values: "IValues";
meta: "IMeta";
} & {
[K in T]: {
entity: TypeMap[K];
setEntity: (entity: TypeMap[K]) => void;
};
}[T];
We are accepting a generic argument that is constrained to be a key of TypeMap
and by using a mapped type we create an object in the shape of type: {entity, setEntity}
. Then by using the generic argument, we get the desired attribute.
Last, but not least is to turn our component into a generic component:
export const CreateField = <T extends keyof TypeMap>(props: IProps<T>) => {
props.setEntity(props.entity); // no error
};
If you hover over props.setEntity
you will see that it accepts an argument of type TypeMap[T]
and the props.entity
has exactly that type. In short, we restrict the compiler from narrowing to the exact type which does the trick.