TypeScript Discriminated Unions problem with setState

74 Views Asked by At

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!

1

There are 1 best solutions below

0
wonderflame On BEST ANSWER

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.

playground