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

A common TypeScript error React developers will encounter

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

Did you find this article valuable?

Support Ákos Kőműves by becoming a sponsor. Any amount is appreciated!