Daryn K.
Daryn K.

Reputation: 691

Flow doesn't understand checks for null or undefined

Now I'm working with Flow types generated by Apollo Codegen. It's very useful tool, but all types are "maybe" ones. And now I have the next problem: please, consider very minified and simple example (link to Flow try):

/* @flow */
type Type1 = {|  // <- example of Flow-type generated by Apollo Codegen
  prop?: ?string
|}

type Type2 = { // <- example of my Flow-type
  prop: string
}

function bar(y: Type2): void {
  console.log(y.prop)
}

function foo(x: Type1): void {
  if (x && x.prop && typeof x.prop === 'string') { // <- I'm trying to check the "x" argument
    bar(x)
  }
}

foo({ prop: 'hello' })

Flow doesn't understand my checks and shows the error because of null or undefined. How to manage that?

Upvotes: 0

Views: 1875

Answers (2)

josephjnk
josephjnk

Reputation: 348

The reason that this doesn't work as a type refinement is that your check can pass for both Type1 and Type2. Flow does the conditional check, but the check can pass for a value of Type1, so the conditional does not refine the type of x at all. Then inside the conditional it sees you trying to pass a value of Type1 to Type2.

To understand Flow's type refinements, it's important to remember that Flow doesn't carry additional refinement data on a variable other than that variable's type. Here is an example which is similar to yours, but without optional fields. In the valid function, we check the type of y.field, and then use y.field once we know its type. In the invalid function, we check the type of y.field, and then try to use y. But the type refinement didn't change the type of y here, only of y.field, so we get a type error.

As a counterpoint, here is an example where we do a type refinement based on the value of a field. What's important to note is that inside of the f function, the variable z has the type Baz | Qux, and that our refinement is on a field which does not overlap between those two. In this case the refinement turns the type Baz | Qux into the type Baz, and we can call our second function without error.

To get around this limitation in your case, we need to break down the value x and put the field that we're refining into its own variable. Then, once we know the type of this variable, we create a value that we know has type Type2 and pass that into the desired function. Here is the most direct way of doing that.

Because this kind of thing comes up often, I have a pattern of creating "conversion" functions to turn potentially unclean data into clean data, so that other functions don't have to bother with the complexity of refinements. In this case I would do so like this.

This pattern of factoring out conversion functions can also be very helpful when sanitizing deeply nested data, or converting nested objects from type mixed into known types. It is much easier to create a conversion function for a large type by combining conversion objects for small types than it is to write one from scratch.

Upvotes: 2

Lyle Underwood
Lyle Underwood

Reputation: 1304

In this example you're refining the type of the property prop, you're not refining x. Refinement in flow can only really be used for top-level types. See this question for some alternate strategies.

Upvotes: 0

Related Questions