FAQ
I checked that foo.bar
is not null
, but Flow still thinks it is. Why does this happen and how can I fix it?
Flow does not keep track of side effects, so any function call may potentially nullify your check. This is called refinement invalidation. Example:
1type Param = {2 bar: ?string,3}4function myFunc(foo: Param): string {5 if (foo.bar) {6 console.log("checked!");7 return foo.bar; // Flow errors. If you remove the console.log, it works 8 }9
10 return "default string";11}
7:12-7:18: Cannot return `foo.bar` because null or undefined [1] is incompatible with string [2]. [incompatible-return]
You can get around this by storing your checked values in local variables:
1type Param = {2 bar: ?string,3}4function myFunc(foo: Param): string {5 if (foo.bar) {6 const bar = foo.bar;7 console.log("checked!");8 return bar; // Ok!9 }10
11 return "default string";12}
I checked that my value is of type A, so why does Flow still believe it's A | B?
Refinement invalidation can also occur variables are updated:
1type T = string | number;2
3let x: T = 'hi';4
5function f() {6 x = 1;7}8
9if (typeof x === 'string') {10 f();11 x as string; 12}
11:3-11:3: Cannot cast `x` to string because number [1] is incompatible with string [2]. [incompatible-cast]
A work around would be to make the variable const
and refactor your code to avoid the reassignment:
1type T = string | number;2
3const x: T = 'hi';4
5function f(x: T): number {6 return 1;7}8
9if (typeof x === 'string') {10 const xUpdated = f(x);11 xUpdated as number;12 x as string;13}
I'm in a closure and Flow ignores the if check that asserts that foo.bar
is defined. Why?
In the previous section we showed how refinements are lost after a function call. The exact same thing happens within closures, since Flow does not track how your value might change before the closure is called:
1const people = [{age: 12}, {age: 18}, {age: 24}];2const oldPerson: {age: ?number} = {age: 70};3if (oldPerson.age) {4 people.forEach(person => {5 console.log(`The person is ${person.age} and the old one is ${oldPerson.age}`); 6 })7}
5:67-5:79: Cannot coerce `oldPerson.age` to string because null or undefined [1] should not be coerced. [incompatible-type]
The solution here is to move the if check in the forEach
, or to assign the age
to an intermediate variable:
1const people = [{age: 12}, {age: 18}, {age: 24}];2const oldPerson: {age: ?number} = {age: 70};3if (oldPerson.age) {4 const age = oldPerson.age;5 people.forEach(person => {6 console.log(`The person is ${person.age} and the old one is ${age}`);7 })8}
But Flow should understand that this function cannot invalidate this refinement, right?
Flow is not complete, so it cannot check all code perfectly. Instead, Flow will make conservative assumptions to try to be sound.
Why can't I use a function in my if-clause to check the type of a property?
Flow doesn't track refinements made in separate function calls:
1const add = (first: number, second: number) => first + second;2const val: string | number = 1;3const isNumber = (x: mixed): boolean => typeof x === 'number';4if (isNumber(val)) {5 add(val, 2); 6}
5:7-5:9: Cannot call `add` with `val` bound to `first` because string [1] is incompatible with number [2]. [incompatible-call]
However, you can annotate your function with a type guard to get this behavior:
1const add = (first: number, second: number) => first + second;2const val: string | number = 1;3// Return annotation updated:4const isNumber = (x: mixed): x is number => typeof x === 'number';5if (isNumber(val)) {6 add(val, 2);7}
Why can't I pass an Array<string>
to a function that takes an Array<string | number>
The function's argument allows string
values in its array, but in this case Flow prevents the original array from receiving a number
.
Inside the function, you would be able to push a number
to the argument array, causing the type of the original array to no longer be accurate.
You can fix this error by changing the type of the argument to $ReadOnlyArray<string | number>
, making it covariant.
This prevents the function body from pushing anything to the array, allowing it to accept narrower types.
As an example, this would not work:
1const fn = (arr: Array<string | number>) => {2 arr.push(123); // Oops! Array<string> was passed in - now inaccurate3 return arr;4};5
6const arr: Array<string> = ['abc'];7
8fn(arr); // Error!
8:4-8:6: Cannot call `fn` with `arr` bound to `arr` because number [1] is incompatible with string [2] in array element. Arrays are invariantly typed. See https://flow.org/en/docs/faq/#why-cant-i-pass-an-arraystring-to-a-function-that-takes-an-arraystring-number. [incompatible-call]
but with $ReadOnlyArray
you can achieve what you were looking for:
1const fn = (arr: $ReadOnlyArray<string | number>) => {2 // arr.push(321); NOTE! Since you are using $ReadOnlyArray<...> you cannot push anything to it3 return arr;4};5
6const arr: Array<string> = ['abc'];7
8fn(arr);
Why can't I pass {a: string}
to a function that takes {a: string | number}
The function argument allows string
values in its field, but in this case Flow prevents the original object from having a number
written to it.
Within the body of the function you would be able to mutate the object so that the property a
would receive a number
, causing the type of the original object to no longer be accurate.
You can fix this error by making the property covariant (read-only): {+a: string | number}
.
This prevents the function body from writing to the property, making it safe to pass more restricted types to the function.
As an example, this would not work:
1const fn = (obj: {a: string | number}) => {2 obj.a = 123; // Oops! {a: string} was passed in - now inaccurate3 return obj;4};5
6const object: {a: string} = {a: 'str' };7
8fn(object); // Error!
8:4-8:9: Cannot call `fn` with `object` bound to `obj` because number [1] is incompatible with string [2] in property `a`. This property is invariantly typed. See https://flow.org/en/docs/faq/#why-cant-i-pass-a-string-to-a-function-that-takes-a-string-number. [incompatible-call]
but with a covariant property you can achieve what you were looking for:
1const fn = (obj: {+a: string | number}) => {2 // obj.a = 123 NOTE! Since you are using covariant {+a: string | number}, you can't mutate it3 return obj;4};5
6const object: {a: string} = { a: 'str' };7
8fn(object);
Why can't I refine a union of objects?
There are two potential reasons:
- You are using inexact objects.
- You are destructuring the object. When destructuring, Flow loses track of object properties.
Broken example:
1type Action =2 | {type: 'A', payload: string}3 | {type: 'B', payload: number};4
5// Not OK6const fn = ({type, payload}: Action) => {7 switch (type) {8 case 'A': return payload.length; // Error! 9 case 'B': return payload + 10;10 }11}
8:30-8:35: Cannot get `payload.length` because property `length` is missing in `Number` [1]. [prop-missing]
Fixed example:
1type Action =2 | {type: 'A', payload: string}3 | {type: 'B', payload: number};4
5// OK6const fn = (action: Action) => {7 switch (action.type) {8 case 'A': return action.payload.length;9 case 'B': return action.payload + 10;10 }11}
I got a "Missing type annotation" error. Where does it come from?
Flow requires type annotations at module boundaries to make sure it can scale. To read more about that, check out our blog post about that.
The most common case you'll encounter is when exporting a function or React component. Flow requires you to annotate inputs. For instance in this example, Flow will complain:
1export const add = a => a + 1;
1:20-1:20: Cannot build a typed interface for this module. You should annotate the exports of this module with types. Missing type annotation at identifier: [signature-verification-failure]1:20-1:20: Missing an annotation on `a`. [missing-local-annot]1:21-1:20: Cannot build a typed interface for this module. You should annotate the exports of this module with types. Missing type annotation at function return: [signature-verification-failure]
The fix here is to add types to the parameters of add
:
1export const add = (a: number): number => a + 1;
To see how you can annotate exported React components, check out our docs on HOCs.
There are other cases where this happens, and they might be harder to understand. You'll get an error like Missing type annotation for U
For instance, you wrote this code:
1const array = ['a', 'b']2export const genericArray = array.map(a => a)
2:29-2:45: Cannot build a typed interface for this module. You should annotate the exports of this module with types. Cannot determine the type of this call expression. Please provide an annotation, e.g., by adding a type cast around this expression. [signature-verification-failure]
Here, Flow will complain on the export
, asking for a type annotation. Flow wants you to annotate exports returned by a generic function. The type of Array.prototype.map
is map<U>(callbackfn: (value: T, index: number, array: Array<T>) => U, thisArg?: any): Array<U>
. The <U>
corresponds to what is called a generic, to express the fact that the type of the function passed to map is linked to the type of the array.
Understanding the logic behind generics might be useful, but what you really need to know to make your typings valid is that you need to help Flow to understand the type of genericArray
.
You can do that by annotating the exported constant:
1const array = ['a', 'b']2export const genericArray: Array<string> = array.map(a => a)