Home > database >  Typescript discriminated union type not enforcing required second property, when first property is w
Typescript discriminated union type not enforcing required second property, when first property is w

Time:09-15

I have an enum:

export enum Sections {
  Dashboard = "Dashboard",
  Select = "Select",
  Widget = "Widget",
}

I want to build a prop for my react components that is a discriminating union type.

export type ReduxLocation =
  | {
      path: Sections;
      widget?: never;
    }
  | {
      path: Sections.Widget;
      widget: {
        id: string;
        side?: "left" | "right";
      };
    };

Then one of the props of a react component would be of type ReduxLocation:

const MyComponent: React.FC<{ reduxLocation: ReduxLocation }> = () => {
  // return whatever
}

The goal is that if passing a reduxLocation with a path of any of the Sections, no other entry is required in the reduxLocation object. However, if path is Sections.Widget, then the widget entry is required.

const App = () => {
  return (
    <div>
      <MyComponent 
        reduxLocation={{ 
          path: Sections.Dashboard 
        }} 
      />
      <MyComponent 
        reduxLocation={{ 
          path: Sections.Dashboard, 
          widget: { id: 'id' } // <---- errors as expected
        }} 
      />
      <MyComponent 
        reduxLocation={{ 
          path: Sections.Widget, 
          widget: { id: 'id' } 
        }} 
      />
      <MyComponent 
        reduxLocation={{ 
          path: Sections.Widget 
          // <-------------------------- should error but doesnt
        }} 
      /> 
    </div>
  );
}

In the case of the last MyComponent above, I am not getting the desired TS error that when using a path: Sections.Widget, the widget property is then also required. My guess is because using path: Sections.Widget also satisfies the first type in ReduxLocation, as path is still of type Maps.

How can I achieve the desired result, where the above case enforces the correct combination of properties in the prop?

CodePudding user response:

The object { path: Sections.Widget } is totally valid since Sections.Widget is assignable to Sections which fulfills the constraints of the first union element.

You would want to exclude Sections.Widget from the path of the first element. That way, the union can be propertly discriminated since each Section is only assignable to one union element.

export type ReduxLocation =
  | {
      path: Exclude<Sections, Sections.Widget>;
      widget?: never;
    }
  | {
      path: Sections.Widget;
      widget: {
        id: string;
        side?: "left" | "right";
      };
    };

Playground

  • Related