Home > OS >  How to make typescript know my variable is not undefined anymore
How to make typescript know my variable is not undefined anymore

Time:12-21

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"
}

Playground link to code

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
}
  • Related