reactjstypescriptdiscriminated-union

TypeScript Discriminated Unions problem with setState


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!


Solution

  • 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