Here is my case:
I am using a Remix loader which get params from the URL but the params are defined as string | undefined
If the variable is undefined
I would like to throw a redirection
export const loader: LoaderFunction = async ({ params }) => {
const { myVar } = params; // myVar: string | undefined
definedOrRedirect(myVar, "/"); // Checks if undefined
return fetchSomething(myVar); // myVar: string | undefined
};
Is there a way to make typescript know that if it didn't throw myVar
is not undefined?
CodePudding user response:
You can make throwIfUndefined
an assertion function to narrow the type of its argument to something that does not include undefined
in its domain. An assertion function has an assertion type predicate of the form asserts x is Y
as its return type, where x
is the name of one of the function's parameters, and Y
is the subtype of typeof X
that we narrow x
to assuming the function returns successfully. Assertion functions cannot return a defined value; they are basically void
-returning functions.
For throwIfUndefined
, here's the normal way to do it, as a function statement:
function throwIfUndefined<T>(x: T | undefined): asserts x is T {
if (typeof x === "undefined") throw new Error("OH NOEZ");
}
You can also write it as an arrow function, although you need to explicitly annotate the variable with its type for the control flow analysis to happen correctly:
const throwIfUndefined: <T, >(x: T | undefined) => asserts x is T = x => {
if (typeof x === "undefined") throw new Error("OH NOEZ");
}
Either way should work:
const Index = ({ params }: { params: { myVar: string | undefined } }) => {
const { myVar } = params // myVar: string | undefined
// myVar.toUpperCase // <-- error, Object is possibly 'undefined'
throwIfUndefined(myVar);
return myVar.toUpperCase() // no error now
}
try {
console.log(Index({
params: {
myVar: Math.random() < 0.5 ? "hello" : undefined
}
})) // 50% "HELLO"
} catch (e) {
console.log(e); // 50% "OH NOEZ"
}
CodePudding user response:
You could skip definedOrRedirect
and use an explicit check:
export const loader: LoaderFunction = async ({ params }) => {
const { myVar } = params; // myVar: string | undefined
if (myVar == undefined) {
return redirect("/");
}
return fetchSomething(myVar); // myVar: string
}
Or you could keep definedOrRedirect
and declare it with an assertion as jcalz suggests:
function definedOrRedirect(variable, path): asserts variable is string
{
if (variable == undefined) {
return redirect(path);
}
}
export const loader: LoaderFunction = async ({ params }) => {
const { myVar } = params; // myVar: string | undefined
definedOrRedirect(myVar, "/");
return fetchSomething(myVar); // myVar: string
}