skip to Main Content

Having the following interface for a React component:

export interface MyInterface  {
  name: string;
  isEasy?: boolean;
  isMedium?: boolean;
  isHard?: boolean;
}

It must accept maximum one property of the last three (isEasy, isMedium or isHard)

For example

<MyComponent name='John' /> // correct
<MyComponent name='John' isEasy /> // correct
<MyComponent name='John' isEasy isHard /> // incorrect

How can it be done?

Tried to union them like this without success:

interface MyInterface {
  name: string;
}

interface MyInterfaceEasy extends MyInterface {
  isEasy: true;
  isMedium?: never;
  isHard?: never;
}

interface MyInterfaceMedium extends MyInterface {
  isEasy: never;
  isMedium?: true;
  isHard?: never;
}

interface MyInterfaceHard extends MyInterface {
  isEasy: never;
  isMedium?: never;
  isHard?: true;
}

export type ExportedInterface =
  | MyInterfaceEasy
  | MyInterfaceMedium
  | MyInterfaceHard;

When testing it with: <MyComponent name='John' isEasy />

Error:

Types of property 'isEasy' are incompatible.
Type 'boolean' is not assignable to type 'undefined'

2

Answers


  1. I think you can change your code to:

    interface MyBaseInterface {
      name: string;
    }
    
    interface MyInterfaceEasy extends MyBaseInterface {
      isEasy?: boolean;
    }
    
    interface MyInterfaceMedium extends MyBaseInterface {
      isMedium?: boolean;
    }
    
    interface MyInterfaceHard extends MyBaseInterface {
      isHard?: boolean;
    }
    
    export type ExportedInterface =
      | MyInterfaceEasy
      | MyInterfaceMedium
      | MyInterfaceHard;
    

    and it should works.

    Login or Signup to reply.
  2. One way of doing that is to always have one of the difficulties be true and the rest ?: false. That way you can omit all props or all but one. Setting two difficulties will set both to true and therefore they won’t be assignable to ExportedInterface and Typescript will error.

    You can see an example of this here or in the Typescript playground:

    interface MyInterface {
      name: string;
    }
    
    interface MyInterfaceEasy extends MyInterface {
      isEasy?: true;
      isMedium?: false;
      isHard?: false;
    }
    
    interface MyInterfaceMedium extends MyInterface {
      isEasy?: false;
      isMedium?: true;
      isHard?: false;
    }
    
    interface MyInterfaceHard extends MyInterface {
      isEasy?: false;
      isMedium?: false;
      isHard?: true;
    }
    
    export type ExportedInterface =
      | MyInterfaceEasy
      | MyInterfaceMedium
      | MyInterfaceHard;
    
    declare const Component: FC<ExportedInterface>;
    
    // OK
    <Component name='none' />;
    <Component name='easy' isEasy />;
    <Component name='medium' isMedium />;
    <Component name='hard' isHard />;
    // Not OK
    <Component name='mix' isEasy isHard />;
    

    You might also consider taking a different approach where you have one prop that is difficulty?: 'easy' | 'medium' | 'hard'. That would only allow one difficulty.

    You can see that approach here or in the Typescript playground:

    interface MyInterface {
      name: string;
      difficulty?: 'easy' | 'medium' | 'hard';
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search