Type narrowing is one of the most useful and powerful features of TypeScript. Sometimes, however, it can be a bit too complicated.
Consider this simple function that uses Firebase:
export const useSignOut = async () => {
try {
const auth = getAuth()
await signOut(auth)
} catch (err: any) {
alert(err.message)
}
}
If you want to type this properly without any, you’d probably want to do something in the lines of if (err.message !== undefined)
but that fails with an error Object is of type 'unknown'
. It’s hard to do this “correctly” and there are a couple of open issues to make this easier. But luckily, as almost always, there’s also a Stack Overflow question with an answer that works and is easy to implement.
Add these helpers to your project:
function hasKey<K extends string, T extends object>(obj: T, key: K): obj is T & Record<K, unknown> {
return key in obj
}
function isObjectWithKey<T, K extends string>(
obj: T,
key: K
): obj is T & object & Record<K, unknown> {
return typeof obj === 'object' && obj !== null && key in obj
}
and then refactor the original if to:
if (isObjectWithKey(err, 'message')) {
alert(err.message)
}
Both hasKey
and isObjectWithKey
are very useful to have in your everyday TS toolbelt. And speaking of toolbelts, this being the JavaScript world, there obviously is already a package for it; ts-toolbelt has a ton of these kind of helpers, battle-tested and ready to use!