Today I learned, some new typescript today after facing a simple issue.
The issue I faced, I wanted a type that would enforce:
- if defined, both are required.
- else, none are required.
- bonus, you can have other discriminating unions require other types to be included instead of this one.
This would ensure developers do not miss one of the inputs when you need both for the change to work effectively.
<FileUpload
accept="*"
/>
<FileUpload
accept="*"
isDirty={false}
highlightDirty={true}
/>
<FileUpload
accept="*"
oops={'as a developer I force highlight dirty to be a number for an example'}
highlightDirty={0}
/>
<FileUpload
accept="*"
highlightDirty={true}
/>
<FileUpload
accept="*"
isDirty={false}
/>
<FileUpload
accept="*"
isDirty={false}
highlightDirty={0}
/>
This can be accomplished with discriminating unions.
// If defined, both are required.
type IDirtyTrack = {
highlightDirty?: undefined
isDirty?: undefined
} | {
highlightDirty: boolean,
isDirty: boolean
}
interface FileUploadProps {
/* Bunch of props I need here */
}
type ItWorks = FileUploadProps & IDirtyTrack
https://www.typescriptlang.org/docs/handbook/unions-and-intersections.html#discriminating-unions
I have seen the discriminating unions like:
{ state: "failed" | "error"; }
also: { cool: true; }
But never thought about this situation:
{ highlightDirty?: undefined }
Undefined here is a value both a value and a type here. 🤯 (Intentionally I say this wrong here.)
That is not true, actually everything here is a type.
Even { state: "failed" }
is saying, I am a type of "failed"
and only "failed"
.
We could expand this further to take numbers requiring different props:
// If defined, both are required.
type IDirtyTrack = {
highlightDirty?: undefined
isDirty?: undefined
} | {
highlightDirty: boolean,
isDirty: boolean
} | {
highlightDirty: number,
oops: string
}
interface FileUploadProps {
/* Bunch of props I need here */
}
type ItWorks = FileUploadProps & IDirtyTrack
What fun!
Reposting scenerios from above for readability.
<FileUpload
accept="*"
/>
<FileUpload
accept="*"
isDirty={false}
highlightDirty={true}
/>
<FileUpload
accept="*"
oops={'as a developer I force highlight dirty to be a number for an example'}
highlightDirty={0}
/>
<FileUpload
accept="*"
highlightDirty={true}
/>
<FileUpload
accept="*"
isDirty={false}
/>
<FileUpload
accept="*"
isDirty={false}
highlightDirty={0}
/>
I could see this being really useful for libraries that need to have the same component used many different ways.
That's cool, I'll have to remember this one.