How to exclude one field from a type in Typescript when all other fields are provided

Hello, I've got a function named createBreakpointValue that accepts an Object with arguments: desktop, tablet, mobile, allElse

The specific logic I need is as follows: If all arguments are provided (desktop, tablet, mobile), then the allElse argument should be ignored and an error should occur if allElse is included.

If only some arguments are passed (such as mobile), then the allElse argument must be required alongside either desktop & tablet.

This is my current implementation:

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

type Points =
  | {
      desktop: any;
      tablet: any;
      mobile: any;
      allElse?: never;
    }
  | {
      desktop?: any;
      tablet?: any;
      mobile?: any;
      allElse: any;
    };

const currentViewport: keyof Points = "desktop";
const createBreakpointValue = (points: Points): ValueOf<Points> => {
  if (currentViewport in points) {
    return points[currentViewport];
  } else {
    return points.allElse;
  }
};

// valid usage
createBreakpointValue({ allElse: 1 });

//valid usage
createBreakpointValue({ desktop: 1, tablet: 1, mobile: 1 });

// should highlight allElse as invalid
createBreakpointValue({ allElse: 1, desktop: 1, mobile: 1, tablet: 1 });

// should indicate that allElse or mobile must be provided
createBreakpointValue({ desktop: 1, tablet: 1 });

Currently, the typing works correctly up to when all correct arguments and allElse are passed which should prompt a message like "allElse is not valid here".

Sandbox: https://codesandbox.io/s/lucid-breeze-zlgws0?file=/src/index.ts

Answer №1

The issue here is that the value being passed in remains compatible with the last member of your union (the one where all members are optional).

To address this, you can create a union where a member with all properties is incompatible with the union. This can be achieved by expanding the union with variants where at least one property must be undefined:

type Points =
    | {
        desktop?: undefined
        tablet?: any
        mobile?: any
        allElse?: any
    }
    | {
        tablet?: undefined
        desktop?: any
        mobile?: any
        allElse?: any
    }
    | {
        mobile?: undefined
        desktop?: any
        tablet?: any
        allElse?: any
    }
    | {
        allElse?: undefined
        desktop?: any
        tablet?: any
        mobile?: any
    }

Playground Link

You could also potentially generate such a type using mapped types:

type NotAll<T> = {
    [P in keyof T]: Partial<Record<P, undefined>> & Omit<T, P>
}[keyof T]
type Points = NotAll<{
    desktop?: any
    tablet?: any
    mobile?: any
    allElse?: any
}>

Playground Link

Answer №2

I love working with enums

enum DeviceType {
    laptop = 1 << 1,
    smartphone = 1 << 2,
    tablet = 1 << 3,    
    allDevices = laptop | smartphone | tablet
}

const currentDevice: DeviceType = DeviceType.laptop;
const checkDeviceType = (device: DeviceType) => {
        return (device & currentDevice) === currentDevice;
};

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

Tips for preventing the ngbTypeahead input field from automatically opening when focused until all data is fully mapped

When clicking on the input field, I want the typeahead feature to display the first 5 results. I have created a solution based on the ngbTypeahead documentation. app.component.html <div class="form-group g-0 mb-3"> <input id="typ ...

The lite-server is not compatible for initiating the Angular2 Quickstart

Struggling to get the Angular2 Quick start app up and running on my Windows system. Unfortunately, I've hit a roadblock with the "lite-server". Despite installing dependencies (npm install), when attempting to run the app (npm start), an error pops u ...

Improving the process of class initialization in Angular 4 using TypeScript

Is there a more efficient method to initialize an inner class within an outer class in Angular 4? Suppose we have an outer class named ProductsModel that includes ProductsListModel. We need to send the ProductId string array as part of a server-side reque ...

Serve both .ts and .js files to the browser using RequireJs

In my ASP.NET Core Project, the following files are present: greet.ts export class WelcomMesssage { name: string; constructor(name: string) { this.name = name; } say(): void { console.log("Welcome " + this.name); } } GreetExample.ts import * as ...

Sorting an array of objects in TypeScript may result in a type error

My data includes various categories, ages, and countries... const data = [ { category: 'Fish', age: 10, country: 'United Kingdom' }, { category: 'Fish', age: 9, country: 'United Kingdom' }, { category: ...

Injecting a service into a parent class in Angular 6 without the need to pass it through the constructor

Can anyone provide guidance on how to incorporate a service with multiple dependencies into an abstract class that will be inherited by several classes, in a more streamlined manner than passing it through all constructors? I attempted to utilize static m ...

What is the best way to link together a varying amount of observables?

Being new to Angular and Observables, I am looking for a way to efficiently chain the call of a service multiple times. Is there a straightforward method using Observables that can achieve this in a more generic manner than traditional methods? (similar ...

A guide on obtaining the ReturnType within a UseQuery declaration

I am currently working on building a TypeScript wrapper for RTKQ queries that can be used generically. I have made progress on most of my goals, but I am encountering an issue with determining the return type for a generic or specific query. I have shared ...

Ensure that the JSON file containing translations is fully loaded prior to proceeding with the loading of the Angular application

In our Angular application, we have integrated internationalization using ng2-translate. We are utilizing the .instant(...) method for translations to simplify the process without having to subscribe to observables. One issue we are facing is that using . ...

Combining the Partial<CssStyleDeclaration> union type with a dictionary can lead to potential typing complications when the implicit any flag is

Using VueJS v-bind:style binding makes it possible to set CSS variables. I am attempting to create a union type that allows for the object passed to v-bind:style to retain typings for CssStyleDeclaration, while also being relaxed enough to accept an arbitr ...

Using jest to simulate a private variable in your code

I am working on unit testing a function that looks like this: export class newClass { private service: ServiceToMock; constructor () { this.service = new ServiceToMock() } callToTest () { this.service.externalCall().then(()=& ...

Ways to solve VScode gutter indicator glitches after switching text editors?

When my active function is running, I have a specific updateTrigger that ensures certain actions are taken when the activeTextEditor in vscode changes: const updateTrigger = () => { if (vscode.window.activeTextEditor) { updateDecorations(con ...

Is there a way to execute tagged Feature/Scenario/Examples in Webdriverio-cucumber/boilerplate?

Hey there! I could use some assistance. I'm attempting to execute a specific scenario using Cucumber tags with the expression below: npx wdio run wdio.conf.js --cucumberOpts.tagExpression='@sanity and @stage' However, when I run the comman ...

Angular 2: Dynamic URLs for Templates

I am trying to switch between two different sidebars based on a variable obtained from the URL. However, when I attempt to implement this feature, I encounter an error message. @Component({ selector: 'sidebar', templateUrl: './sideb ...

Can someone guide me on leveraging TypeScript typing details provided by a Node.js package?

I've come across various versions of this query, but I'm struggling to comprehend it. I'm currently utilizing the websocket module (which I've used successfully without TypeScript before) and facing difficulties with typing my variables ...

Dynamic Type in Typescript Record

Looking for a way to attach types to record names in a class that returns a Record. The current code snippet is as follows: interface DataInterface { bar: number; foo: string; fooBar: boolean; } export class MyClass { public bar: number; p ...

DiscordJS - Error: Access Denied

After following the worn off keys tutorial for a Discord bot, I encountered an error that says: /home/container/node_modules/discord.js/src/rest/RequestHandler.js:349 throw new DiscordAPIError(data, res.status, request); ^ DiscordAPIError: Miss ...

How to Transfer FormGroup Object from Child Component to Parent Component in Angular

In the realm of form creation, I find myself facing a dilemma with two components. My child component holds a crucial FormGroup object, defined as such: employeeForm : FormGroup; this.employeeForm = new FormGroup({ firstName:new FormControl(), ...

Incorporate a personalized add-button into the material-table interface

My current setup includes a basic material-table structured like this: <MaterialTable options={myOptions} title="MyTitle" columns={state.columns} data={state.data} tableRef={tableRef} // Not functioning properly editabl ...

Dealing with performance issues in React Recharts when rendering a large amount of data

My Recharts use case involves rendering over 20,000 data points, which is causing a blocking render: https://codesandbox.io/s/recharts-render-blocking-2k1eh?file=/src/App.tsx (Check out the CodeSandbox with a small pulse animation to visualize the blocki ...