Home > database >  Typescript: Omit 1 field from type if all others are passed
Typescript: Omit 1 field from type if all others are passed

Time:04-29

Hi there i have a function called createBreakpointValue that takes an Object of arguments: desktop, tablet, mobile, allElse

The type-logic i am requiring is: If all are passed (desktop, tablet, mobile), then allElse is irrelevant and should error if allElse is passed.

If some are passed (mobile), then allElse should be required or desktop & tablet.

What i currently have:

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;
  }
};

// correct
createBreakpointValue({ allElse: 1 });

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

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

// should highlight saying allElse or mobile should be provided
createBreakpointValue({ desktop: 1, tablet: 1 });

At the moment the typing is correct until i pass all correct arguments as well as allElse which i would expect to be highlighted saying something like "allElse is not valid here"

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

CodePudding user response:

Th problem is that the value passed in is still compatible with the last member of your union (the one with all members optional)

You could create a union where a member with all properties is incompatible iwth the union, by having the union expand 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 can probably also 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

CodePudding user response:

I like enums

enum ViewPort {
    desktop = 1 << 1,
    tablet = 1 << 2,
    mobile = 1 << 3,    
    allElse = desktop | tablet | mobile
}

const currentViewport: ViewPort = ViewPort.desktop;
const createBreakpointValue = (points: ViewPort) => {
        return (points & currentViewport) === currentViewport;
};
  • Related