I frequently use string enums in my projects due to their safety and their ability to provide useful autocompletion and extra type safety. However, a common issue I encounter is having to write separate type check functions for each enum when dealing with unknown variables. This is particularly true when working with select inputs that often pass values as strings to an onChange
function.
I will use following enum in my examples
enum Parent {
mom = 'Mom',
dad = 'Dad',
}
Usually my onChange
function looks as follows
function isParent(value: unknown): value is Parent {
return Object.values(Parent).includes(value as Parent);
}
function onChange(value: string) {
if (isParent(value)) {
// value is of type Parent
}
}
Solution 1
As you can imagine this approach requires a separate type check function for each enum used in my project. I wanted to find a more streamlined solution. To achieve this I decided to create an abstraction for isSomeEnum
, which ultimately led to the following solution:
export const getIsEnumTypeChecker =
<E extends Record<string, string>>(e: E): ((v: unknown) => v is E) =>
(v): v is E =>
Object.values(e).includes(v as E[keyof E]);
Although this solution is effective, I still need to create a separate type check function for each enum.
const isParent = getIsEnumTypeChecker(Parent)
function onChange(value: string) {
if (isParent(value)) {
// value is of type Parent
}
}
Solution 2
My final idea is a function that invokes a callback only if the passed variable is a specific enum.
const doIfIsCertainEnum = <E extends Record<string, string>>(
value: unknown,
targetEnum: E,
callback: (v: E[keyof E]) => void
) => {
const isCertainEnum = (v: unknown): v is E[keyof E] =>
Object.values(targetEnum).includes(v as E[keyof E]);
if (isCertainEnum(value)) {
callback(value);
}
};
function onChange(value: string) {
doIfIsCertainEnum(value, Parent, (parent) => {
// parent is of type Parent
});
}
Summary
We can conclude here, but it’s worth mentioning that the final solution can be further improved. Specifically, it could incorporate a second, optional callback to handle cases where the variable has a type other than the passed enum.