How to refactor long union type?

54 views Asked by At

I want to create a React functional component which takes one of the predefined components as a prop and which logic depends on what component was passed in.

So these are the possible names:

enum ComponentName {
  ComponentA: "component.a",
  ComponentB: "component.b",
  ComponentC: "component.c",
  // etc
}

Now the hard part, I have a bunch of Component types, which names are all in the enum, like:

type ComponentA = {
  attributes: {
    // ...
  }
}

type ComponentB = {
  attributes: {
    // ...
  }
}

Now what I want to do is somehow to define type Component, so that:

type Component = 
| {
    id: string;
    name: ComponentName.ComponentA;
    component: ComponentA;
  }
| {
    id: string;
    name: ComponentName.ComponentB;
    component: ComponentB;
  }
| // ...

Is there a way to simplify this union type by mapping over ComponentName enum and matching enum prop to Component type name?

1

There are 1 answers

0
c0m1t On BEST ANSWER

I think if you have some sort of mappings between component names and their attributes ComponentMappings, then you can create a union from that mapping using a helper function like

type Values<T> = T[keyof T];

Values generates a union type from the values of a mapped type or object.

enum ComponentName {
  ComponentA = "component.a",
  ComponentB = "component.b",
  ComponentC = "component.c",
}

type ComponentMappings = {
  [ComponentName.ComponentA]: {
    attributes: {
      className: string;
    };
  };
  [ComponentName.ComponentB]: {
    attributes: {
      className: string;
      foo: number;
    };
  };
  [ComponentName.ComponentC]: {
    attributes: {
      className: string;
    };
  };
};

type Values<T> = T[keyof T];

type Component = Values<{
  [K in ComponentName]: {
    id: string;
    name: K;
    component: ComponentMappings[K];
  };
}>;

const comopnentA: Component = {
  name: ComponentName.ComponentA,
  id: "component-a",
  component: {
    attributes: {
      className: "flex",
    },
  },
};

const componentB: Component = {
  name: ComponentName.ComponentB,
  id: "component-B",
  component: {
    attributes: {
      className: "flex",
      foo: 3,
    },
  },
};

TypesSript Playground

Note that Prettify type in the playground does nothing and just makes it more readable when you hover on the type.