'Typescript: Omit 1 field from type if all others are passed
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
Solution 1:[1]
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
}
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
}>
Solution 2:[2]
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;
};
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | Titian Cernicova-Dragomir |
| Solution 2 | Manfred Wippel |
