Go to all posts

How to use the Omit utility type to enhance existing DOM Interfaces

Jump to heading Checking the inputs that matter

I needed to check whether an input was valid. I could check only the old faithful:

input.validity.valid === true

Problem is I needed to check this input alongside inputs I need to ignore. Those types being hidden, password, and search.

If I was using Vanilla JavaScript, I would add a property to the input element;

input.__ignore = ['hidden','search','password'].includes(input.type);

Then in my validation loop, look for input.__ignore to be true, and continue.

Since I was using TypeScript, I couldn't do that so easily. I'd rather build on top of an existing interface than try and keep track of my own.

Jump to heading Have you heard of ValidityState?

ValidityState is a built-in interface that comes with <input>, you can check it out when you log input.validity. The interface looks like this:

interface ValidityState {
valid: boolean;
badInput: boolean;
customError: boolean;
patternMismatch: boolean;
rangeOverflow: boolean;
rangeUnderflow: boolean;
stepMismatch: boolean;
typeMismatch: boolean;
valueMissing: boolean;
}

This is exactly what I need (and some of what I don't).

Looking through the TypeScript Handbook, I found what I was looking for with the utility type Omit.

Jump to heading Using Omit

Say you have interfaces like this:

interface UserProperties {
name: string;
age: number;
birthday: number;
}

interface UserNumberProperties {
age: number;
birthday: number;
}

Because we've been inundated with articles about not repeating ourselves, your eye is probably twitching. Fear not, we have a solution: Omit.

interface UserProperties {
name: string;
age: number;
birthday: number;
}

interface UserNumberProperties extends Omit<UserProperties, 'name'>;

Now our interface UserNumberProperties only contains age and birthday. Now how did I apply this to ValidityState?

interface Validity extends Omit<ValidityState, 'valid'>;

Wait that doesn't solve the problem though? Now your interface doesn't even expect anything to be valid or not.

Jump to heading Building on top of Omit

We can add the omitted interface by appending an object with our extra property.

interface Validity extends Omit<ValidityState, 'valid'>{
valid: boolean | 'ignore';
}

This doesn't apply only to properties that already existed on the original interface either. The initial ValidityState interface doesn't let you get values using ValidityState['key']. We can fix this though!

interface Validity extends Omit<ValidityState, 'valid'>{
valid: boolean | 'ignore';
mySuperSpecialCustomValidation: () => void;
[index: string]: boolean | string | () => void;
}

Note: If we weren't adding the 'ignore' value as an option for Validity.valid or the callback function, then our index would be [index:string]: boolean;

I had my dream ValidityState interface in 4 lines of code. With the added benefit that I was building on top of the existing JavaScript interfaces that exist.