Skip to content

Instantly share code, notes, and snippets.

@LevPewPew
Last active March 20, 2022 22:55
Show Gist options
  • Select an option

  • Save LevPewPew/818043c1ccd5201dab225c982f2df719 to your computer and use it in GitHub Desktop.

Select an option

Save LevPewPew/818043c1ccd5201dab225c982f2df719 to your computer and use it in GitHub Desktop.
RequireAtLeastOne and RequireOnlyOne custom utility types
type RequireAtLeastOne<T, Keys extends keyof T = keyof T> =
Pick<T, Exclude<keyof T, Keys>>
& {
[K in Keys]-?: Required<Pick<T, K>> & Partial<Pick<T, Exclude<Keys, K>>>
}[Keys]
// ************************************************************
interface MenuItem {
title: string;
component?: number;
click?: number;
icon: string;
}
type ClickOrComponent = RequireAtLeastOne<MenuItem, 'click' | 'component'>
const withComponent: ClickOrComponent = {
title: "test",
component: 52,
icon: "icon"
}
const withClick: ClickOrComponent = {
title: "test",
click: 54,
icon: "icon"
}
const errorWithNeither: ClickOrComponent = {
title: "test",
icon: "icon"
}
const noErrorWithBoth: ClickOrComponent = {
title: "test",
click: 54,
component: 24,
icon: "icon"
}
const errorWithBothWhenOneHasWrongType: ClickOrComponent = {
title: "test",
click: 54,
component: "should be number here",
icon: "icon"
}
// ************************************************************
interface NothingIsRequired {
foo?: string;
bar?: string;
fizz?: number;
buzz?: number;
}
type MustHaveOneOrMore = RequireAtLeastOne<NothingIsRequired>
const withFoo: MustHaveOneOrMore = {foo: "something"}
const withAll: MustHaveOneOrMore = {foo: "something", bar: "something else", fizz: 123, buzz: 789}
const errorWithoutAny: MustHaveOneOrMore = {}
const errorWithoutAnyCorrect: MustHaveOneOrMore = { wrong: "something", alsoWrong: "something else"}
const errorWithOneCorrectKeyButWrongType: MustHaveOneOrMore = { foo: 123, bar: "something"}
// ************************************************************
interface NothingIsRequired {
foo?: string;
bar?: string;
fizz?: number;
buzz?: number;
}
type MustHaveOneOrMoreSpecific = RequireAtLeastOne<NothingIsRequired, "foo" | "buzz">
const withFooAndBuzz: MustHaveOneOrMoreSpecific = {foo: "something", buzz: 123};
const withOnlyFoo: MustHaveOneOrMoreSpecific = {foo: "something"};
const errorWithOnlyBar: MustHaveOneOrMoreSpecific = {bar: "asd"};
const withAll2: MustHaveOneOrMoreSpecific = {foo: "something", bar: "something else", fizz: 123, buzz: 789}
const errorWithoutAny: MustHaveOneOrMoreSpecific = {}
const errorWithoutAnySpecifiedOptionals: MustHaveOneOrMoreSpecific = { fizz: 123, bar: "something else"}
const errorWithOneCorrectKeyButWrongType: MustHaveOneOrMoreSpecific = { foo: 123, buzz: 345}
// ************************************************************
type RequireOnlyOne<T, Keys extends keyof T = keyof T> =
Pick<T, Exclude<keyof T, Keys>>
& { [K in Keys]-?:
Required<Pick<T, K>>
& Partial<Record<Exclude<Keys, K>, undefined>>
}[Keys]
// ************************************************************
type OnlyOneClickOrComponent = RequireOnlyOne<MenuItem, 'click' | 'component'>
const noErrorWithOnlyOne: OnlyOneClickOrComponent = {
title: "test",
click: 534,
icon: "icon"
}
const errorWithBoth: OnlyOneClickOrComponent = {
title: "test",
click: 534,
component: 53,
icon: "icon"
}
// ************************************************************
// WARNING:
// WARNING:
// WARNING:
// ************************************************************
// This interface will be used to trick OnlyOneClickAtComponent into allowing an object with both
interface ClickMenuItem {
title: string;
click: number;
icon: string;
}
const hasBoth = {
title: "test",
click: 54,
component: 24,
icon: "icon"
}
// No error because excess properties are only disallowed when directly assigning an object literal
const temp: ClickMenuItem = hasBoth
// No error despite temp actually having both because TS only knows that temp is a ClickMenuItem
const trickIntoAllowingBoth: OnlyOneClickOrComponent = temp
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment