DaKaZ
DaKaZ

Reputation: 935

Why does flowtype think my variable is undefined?

I have a flowtype object declaration that looks like this:

   type ProjectType = {
     // removed for brevity
     releases?: Array<ReleaseType>
   }

There are times when releases is not included in the object, so when I want to use it, I first have a conditional checking for it. So here, later in my code, I access that array after a conditional like this:

   if (selectedProject
      && selectedProject.releases
      && selectedProject.releases.length) {
      majorReleasesSet = new Set(selectedProject.releases.map((release: ReleaseType): string => (
        release.major)));
      projectHasNoReleases = false;
      projectLatestMajorRelease = selectedProject.releases.slice(-1)[0].major;
      projectLatestMinorRelease = selectedProject.releases.slice(-1)[0].minor;
    }

But flow does not like this, complaining:

Cannot call selectedProject.releases.slice because property slice is missing in undefined [1].

     components/TimeEntries/EntryForm.jsx
     123│         release.major)));
     124│       projectHasNoReleases = false;
     125│       projectLatestMajorRelease = selectedProject.releases.slice(-1)[0].major;
     126│       projectLatestMinorRelease = selectedProject.releases.slice(-1)[0].minor;
     127│     }
     128│
     129│     const projectLatestRelease = `${projectLatestMajorRelease}.${projectLatestMinorRelease}`;

What in the world am I missing? The I tried adding Array.isArray(selectedProject.releases) but flow still complained. Flow lists errors on both lines 125 and 126.

Upvotes: 1

Views: 277

Answers (1)

James Kraus
James Kraus

Reputation: 3478

I would say that the the fact that you do some potentially effectful things (running a map function, making a set, etc) makes Flow concerned that the releases property might have changed on the object. Flow will discard basically any of your refinements after you run a function.

Here's an example of your code, as-is, throwing an error

The easiest way to get around this is to pull the releases off of your object into a separate value and perform the null check against that. That way, Flow is sure that it's still not null:

(Try)

// Mock this
type ReleaseType = {
  major: string;
  minor: string,
}

type ProjectType = {
  // removed for brevity
  releases?: Array<ReleaseType>
}

var selectedProject: ProjectType = {
  major: "foo",
  minor: "bar",
}

// Set up some variables for this example since I
// don't have any context on them
declare var majorReleasesSet: Set<any>;
declare var projectHasNoReleases: any;
declare var projectLatestMajorRelease: any;
declare var projectLatestMinorRelease: any;

// The `const {releases} = whateverObject` is the important part here
const {releases} = selectedProject || {};
if (releases && releases.length) {
  majorReleasesSet = new Set(releases.map((release: ReleaseType): string => (release.major)));
  projectHasNoReleases = false;
  projectLatestMajorRelease = releases.slice(-1)[0].major;
  projectLatestMinorRelease = releases.slice(-1)[0].minor;
}

Upvotes: 1

Related Questions