Skip to content
How to solve ”variable is possibly undefined” in TypeScript - even when it’s defined

How to solve ”variable is possibly undefined” in TypeScript - even when it’s defined

Updated: at 03:11 PM

Readable code has always been one of the highest importance in my teams.

But it’s not reduced to ”correctly” naming variables.

We care about patterns, nesting, and interaction between different parts of the system - everything that makes navigating the codebase easier.

Nesting is one of those things that I always approach with:

Ughhh, let’s go a level deeper

It’s one of the overused things in React, and it usually appears as a byproduct of using the ternary operator.

It is not uncommon that we find components like this:

interface Player {
  id: number;
  name: string;
}

interface BoardProps {
  players?: Array<Player>;
}

export default function Board({ players }: BoardProps) {
  return (
    <p>
      {players && players.length > 0
        ? players.length > 1
          ? `Welcome our new players: ${players
              .map(({ name }) => name)
              .join(", ")}!`
          : `Welcome our new player: ${players[0].name}!`
        : "No Players"}
    </p>
  );
}

This code works perfectly fine yet makes you question many things. And it's also super ugly...

The ternary operator inevitably leads to nesting, and more ternary operators lead to more nesting.

We decide to refactor the above code and create separate functions to avoid using the ternary operators:

function renderWelcome() {
  if (players.length > 1) {
    const names = players.map(({ name }) => name).join(", ");
    return `Welcome our new players ${names}!`;
  }
  return `Welcome our new player: ${players[0].name}!`;
}

But there's one problem with this:

Even though we check that players is defined before entering renderWelcome TypeScript still warns us about a possibly undefined value for players.

The behavior makes sense to me because nothing prevents renderWelcome from being called without wrapping it with a check - leading to a runtime error in your code.

There are a couple of ways to fix this:

  1. move the if (players) check inside renderWelcome

  2. pass players as an argument to renderWelcome and make it not optional

  3. create a RenderWelcome component

And there's a 3rd way to fix it, by adding players! , but let’s not talk about lazy TypeScript. 😄

I'll go with option 2) because, to me, it looks the best in React.

All you have to do is create a component where players is not optional (note the missing ?):

function PlayerWelcome({ players }: PlayerWelcomeProps) {
  if (players.length > 1) {
    const names = players.map(({ name }) => name).join(", ");
    return <p>Welcome our new players {names}!</p>;
  }
  return <p>Welcome our new player: {players[0].name}!</p>;
}

TypeScript will be happy with this code because players is guaranteed to be there:

Congratulations!

You just learned how you could both improve the readability of your code and create a nice component separation in React when using TypeScript without dirty tricks such as the !.

Thanks for reading my article!

If you liked it, share it with someone who would find it helpful and give the article a ❤️!

Until next time,

Akos

Generative AI with React JS: Build and Deploy Powerful AI Apps

Learn how to leverage the OpenAI API in React to create advanced generative AI applications. Throughout the book, you'll cover topics like generating text, speech post-processing, building a social media companion app, and deploying the final application.

Learn More