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 (https://flow.org/try):
// @flow type Param = { bar: ?string, } function myFunc(foo: Param): string { if (foo.bar) { console.log("checked!"); return foo.bar; // Flow errors. If you remove the console.log, it works } return "default string"; }
You can get around this by storing your checked values in local variables:
// @flow type Param = { bar: ?string, } function myFunc(foo: Param): string { if (foo.bar) { const bar = foo.bar; console.log("checked!"); return bar; // Ok! } return "default string"; }
Refinement invalidation can also happen with disjoint unions. Any function call will invalidate any refinement.
Example (https://flow.org/try):
// @flow type Response = | { type: 'success', value: string } | { type: 'error', error: Error }; const handleResponse = (response: Response) => { if (response.type === 'success') { setTimeout(() => { console.log(`${response.value} 1`) }, 1000); } };
Here, a work around would be to extract the part of the value you’re interested in, or to move the if check inside the setTimeout
call:
Example (https://flow.org/try):
// @flow type Response = | { type: 'success', value: string } | { type: 'error', error: Error }; const handleResponse = (response: Response) => { if (response.type === 'success') { const value = response.value setTimeout(() => { console.log(`${value} 1`) }, 1000); } };
foo.bar
is defined. Why? In the previous section we showed how refinement is 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.
Example (https://flow.org/try):
// @flow type Person = {age: ?number} const people = [{age: 12}, {age: 18}, {age: 24}]; const oldPerson: Person = {age: 70}; if (oldPerson.age) { people.forEach(person => { console.log(`The person is ${person.age} and the old one is ${oldPerson.age}`); }) }
The solution here is to move the if check in the forEach
, or to assign the age
to an intermediate variable.
Example (https://flow.org/try):
// @flow type Person = {age: ?number} const people = [{age: 12}, {age: 18}, {age: 24}]; const oldPerson: Person = {age: 70}; if (oldPerson.age) { const age = oldPerson.age; people.forEach(person => { console.log(`The person is ${person.age} and the old one is ${age}`); }) }
Flow is not complete, so it cannot check all code perfectly. Instead, Flow will make conservative assumptions to try to be sound.
Flow doesn’t track refinements made in separated function calls.
Example (https://flow.org/try)
// @flow const add = (first: number, second: number) => first + second; const val: string | number = ... const isNumber = (valueToRefine: ?number) => typeof valueToRefine === 'number'; if (isNumber(val)) add(val, 2);
However, Flow has predicates functions that can do these checks via %checks
.
Example (https://flow.org/try)
// @flow const add = (first: number, second: number) => first + second; const val: string | number = ... const isNumber = (valueToRefine: ?number): %checks => typeof valueToRefine === 'number'; if (isNumber(val)) add(val, 2);
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>
. This prevents the function body from pushing anything to the array, allowing it to accept narrower types.
As an example, this would not work:
// @flow const fn = (arr: Array<string | number>) => { // arr.push(123) NOTE! Array<string> passed in and after this it would also include numbers if allowed return arr; }; const arr: Array<string> = ['abc']; fn(arr); // Error!
but with $ReadOnlyArray
you can achieve what you were looking for:
// @flow const fn = (arr: $ReadOnlyArray<string | number>) => { // arr.push(321) NOTE! Since you are using $ReadOnlyArray<...> you cannot push anything to it return arr; }; const arr: Array<string> = ['abc']; fn(arr);
Example (https://flow.org/try)
{ 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:
// @flow const fn = (obj: {| a: string | number |}) => { // obj.a = 123; return obj; }; const object: {| a: string |} = {a: 'str' }; fn(object); // Error!
but with a covariant property you can achieve what you were looking for:
// @flow const fn = (obj: {| +a: string | number |}) => { // obj.a = 123 NOTE! Since you are using covariant {| +a: string | number |}, you can't mutate it return obj; }; const object: {| a: string |} = { a: 'str' }; fn(object);
Example (https://flow.org/try)
There are two potential reasons:
Broken example:
/* @flow */ type Action = | {type: 'A', payload: string} | {type: 'B', payload: number}; // Not OK const fn = ({type, payload}: Action) => { switch (type) { case 'A': return payload.length; case 'B': return payload + 10; } }
Fixed example:
/* @flow */ type Action = | {type: 'A', payload: string} | {type: 'B', payload: number}; // OK const fn = (action: Action) => { switch (action.type) { case 'A': return action.payload.length; case 'B': return action.payload + 10; } }
Second example:
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:
export const add = a => a + 1;
The fix here is to add types to the parameters of add
.
Example (https://flow.org/try):
export const add = (a: 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:
const array = ['a', 'b'] export const genericArray = array.map(a => a)
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 adding an explicit type argument:
const array = ['a', 'b']; export const genericArray = array.map<string>(a => a);
or by annotating the exported constant (https://flow.org/try):
const array = ['a', 'b'] export const genericArray: Array<string> = array.map(a => a)
Typings HOCs can be complicated. While you can follow the docs about it, sometimes it can be easier to type the returned component.
For instance, in this example, we don’t type the HOC (setType), but the component created with it, Button
. To do so, we use the type React.ComponentType
.
// @flow import * as React from 'react'; const setType = BaseComponent => props => <BaseComponent {...props} type="button" />; const GenericButton = ({type, children}) => <button type={type} onClick={() => console.log('clicked')}>{children}</button>; const Button: React.ComponentType<{children: React.Node}> = setType(GenericButton);
© 2013–present Facebook Inc.
Licensed under the MIT License.
https://flow.org/en/docs/faq