Tips for ensuring only one property is present in a Typescript interface

Consider the React component interface below:

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

This component must accept only one property from isEasy, isMedium, or isHard

For example:

<MyComponent name='John' /> // valid
<MyComponent name='John' isEasy /> // valid
<MyComponent name='John' isEasy isHard /> // invalid

How could this be achieved?

An attempt was made to union them as shown below 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 tested with:

<MyComponent name='John' isEasy />

The following error was encountered:

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

Answer №1

To ensure one difficulty is always true and the rest are set to false, you can use a ternary operator in your props. This approach allows you to omit all props except for one. Setting two difficulties to true will result in both being unable to be assigned to ExportedInterface in Typescript, causing an error.

An example of this technique can be found here or on 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 />;

Another approach is to have one prop that specifies the difficulty level as 'easy', 'medium', or 'hard'. This limits the selection to only one difficulty at a time.

You can explore this method here or on the Typescript playground:

interface MyInterface {
  name: string;
  difficulty?: 'easy' | 'medium' | 'hard';
}

Answer №2

To achieve the desired outcome, consider updating your code as follows:

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;

By making these adjustments, your code should function correctly.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Update the Vue component upon fetching new data

How can I continuously refresh the list of items when a button in a sibling component is clicked? The watch method only triggers once, but I need it to constantly refresh. This is the parent element: <template> <div class="container"& ...

Tips for adjusting the location of material-ui's dialog box

Is there a way to adjust the position of the dialog when it is opened in my react app using material-ui? Currently, it always appears centered. Appreciate any help in advance! ...

Why does Popover in Material-UI hide the backdrop of the Modal?

It's puzzling why Popovers are set to have an invisible backdrop by default, with no apparent way to change it. Is there a key concept in Material Design that I'm overlooking? Should I raise this concern as an issue? <Modal container ...

What is the best method for testing routes implemented with the application router in NextJS? My go-to testing tool for this is vitest

Is it possible to test routes with vitest on Next.js version 14.1.0? I have been unable to find any information on this topic. I am looking for suggestions on how to implement these tests in my project, similar to the way I did with Jest and React Router ...

Tips for arranging various information into a unified column within an Antd Table

Is there a way to display multiple data elements in a single cell of an Ant Design table, as it currently only allows insertion of one data element? I am attempting to combine both the 'transactionType' and 'sourceNo' into a single cell ...

Is there a way to end my session in nextAuth on NextJS 14?

Recently, I've started using Typscript with a NextJS14 project that utilizes NextAuth for authentication. While there weren't any errors in the JavaScript version of my code, I encountered an error when working with TypeScript. This is a snippet ...

Prevent encountering the error message "Cannot read property of undefined" while mapping data

When I retrieve data from the Google Books API, I need to transform it into a more manageable array for further processing. To achieve this, I am using the following code snippet: data.items.map(function(book) { return { googleId : book.id, image : book ...

I'm looking for the configuration of function definitions for the Jasmine npm module within an Angular project. Can

When a new Angular project is created, the *.spec.ts files provide access to Jasmine functions such as "describe", "beforeEach", and expect. Despite not having an import clause for them in spec.ts files, I can click on these functions and navigate to their ...

"Optimizing navigation with dynamic link routing in AngularJS

I am working on a list of apartments displayed using ng-repeat. I need each apartment to be a clickable link that directs the user to view more details about that specific apartment. However, I am facing an issue where the links are not being repeated dyna ...

React NextJS CSS - Setting the section's height to 100% of the parent's height results in taking up 100% of the page's height

I've encountered an issue while transferring my project to NextJS from Create-React-App. In NextJS, setting the height of a section to 100% is causing it to take up the entire page height instead of adjusting for the header and footer elements. I nee ...

Does jqgrid navgrid have an event called "on Refresh"?

Is there a way to trigger an event before the grid automatically refreshes? I am looking for something similar to "onSearch" but for the reset button. Below is the code snippet for the navgrid: $("#jqGrid").jqGrid('navGrid','#jqGridPag ...

Issue with linear Graham scan method causing failure when computing convex hull of basic polygon

It is said that the Graham scan algorithm can efficiently find the convex hull of a simple polygon in linear time without requiring the nlogn sorting step since the vertices are already effectively sorted. I have implemented the Graham scan algorithm, and ...

Adjust the size of every card in a row when one card is resized

At the top of the page, I have four cards that are visible. Each card's height adjusts based on its content and will resize when the window size is changed. My goal is to ensure that all cards in the same row have equal heights. To see a demo, visit: ...

Encountering Unmet Dependency Issue When Running 'npm install' on Laravel in Windows 8

Currently, I am in the process of working on a Laravel project and looking to utilize Elixir to handle my front-end tasks. After running 'npm install', I encountered a warning stating "npm WARN unmet dependency". What steps should I take in order ...

Handling dynamic routes with React Router 4 and the 404 path

I have been working with the latest version of React Router (4) and have implemented a dynamic route configuration as described in the tutorial. The routes are functioning correctly, except for when I tried to add a 404 path following the tutorial's i ...

JavaScript code for iframe auto resizing is not functioning properly on Firefox browser

I have implemented a script to automatically resize the height and width of an iframe based on its content. <script language="JavaScript"> function autoResize(id){ var newheight; var newwidth; if(document.getElementById){ newh ...

What's the best way to alter the behavior of scrollToFirstError in antdesign form customization

As stated in the documentation, the scrollToFirstError behavior can be modified by passing a function to it. export interface CustomScrollAction { el: Element; top: number; left: number; } export declare type CustomScrollBehaviorCallback<T&g ...

What is the best way to set up jest to generate coverage reports for Selenium tests?

I recently made the switch to using jest for testing my JavaScript project after encountering coverage issues with mocha and chai. While my unit tests are providing coverage data, my Selenium tests are not. I've tried various solutions found in outdat ...

React Checkbox Tree implemented with the Material-UI and Formik libraries. Elevate your ReactJS development with this

Trying to implement a Checkbox tree using Material-UIs Tree with formik for the form (checkboxes). I'm close to achieving it, but facing an issue where clicking on a parent node checkbox should also check its children nodes. Any suggestions on how to ...

Tired of the premium content on Medium.com. How can I conceal premium posts that are aimed at a specific class within a parent element?

I have noticed that all Premium posts contain an element with the class ="svgIcon-use" <svg class="svgIcon-use" width="15" height="15" viewBox="0 0 15 15" style=""><path d="M7.438 2.324c.034-.099.090 123 0l1.2 3.53a.29.29 0 0 0 .26.19h3.884c.11 0 ...