Aleksandar Dimitrov Aleksandar Dimitrov - 8 months ago 123
Javascript Question

Flow seems to assume wrong type while iterating over Object.entries

EDIT: since this seems to be a bug, I've opened an issue on Github #4997

In the following example, Flow seems to assume that

label
is of type
mixed
even though both the Props declaration, and the anonymous function declaration bind it to
Node
. What am I missing?

/* @flow */
import React from 'react';
import type { Node } from 'react';

type Props = {
values: { [string]: Node },
/* … */
};

export default class SelectButtons extends React.Component<Props> {
/* … */

createButtons(): Array<Node> {
return Object.entries(this.props.values).map(
([value: string, label: Node], index: number): Node => (
<button>
{label}
</button>
),
);
}
}


This is the error message:

v------
44: <button
45: onClick={() => this.choose(value)}
46: key={index}
47: className={this.block('option', { selected: value === this.state.value })()}
48: >
^ React element `button`
v------
44: <button
45: onClick={() => this.choose(value)}
46: key={index}
47: className={this.block('option', { selected: value === this.state.value })()}
48: >
^ React element `button`. This type is incompatible with
v
170: props: {
171: children?: React$Node,
172: [key: string]: any,
173: },
^ object type. See lib: /tmp/flow/flowlib_3bead812/react-dom.js:170
Property `children` is incompatible:
49: {label}
^^^^^ mixed. This type is incompatible with
171: children?: React$Node,
^^^^^^^^^^ union: undefined | null | boolean | number | string | type application of type `React$Element` | type application of identifier `Iterable`. See lib: /tmp/flow/flowlib_3bead812/react-dom.js:171
Member 1:
15: | void
^^^^ undefined. See lib: /tmp/flow/flowlib_3bead812/react.js:15
Error:
49: {label}
^^^^^ mixed. This type is incompatible with
15: | void
^^^^ undefined. See lib: /tmp/flow/flowlib_3bead812/react.js:15
Member 2:
16: | null
^^^^ null. See lib: /tmp/flow/flowlib_3bead812/react.js:16
Error:
49: {label}
^^^^^ mixed. This type is incompatible with
16: | null
^^^^ null. See lib: /tmp/flow/flowlib_3bead812/react.js:16
Member 3:
17: | boolean
^^^^^^^ boolean. See lib: /tmp/flow/flowlib_3bead812/react.js:17
Error:
49: {label}
^^^^^ mixed. This type is incompatible with
17: | boolean
^^^^^^^ boolean. See lib: /tmp/flow/flowlib_3bead812/react.js:17
Member 4:
18: | number
^^^^^^ number. See lib: /tmp/flow/flowlib_3bead812/react.js:18
Error:
49: {label}
^^^^^ mixed. This type is incompatible with
18: | number
^^^^^^ number. See lib: /tmp/flow/flowlib_3bead812/react.js:18
Member 5:
19: | string
^^^^^^ string. See lib: /tmp/flow/flowlib_3bead812/react.js:19
Error:
49: {label}
^^^^^ mixed. This type is incompatible with
19: | string
^^^^^^ string. See lib: /tmp/flow/flowlib_3bead812/react.js:19
Member 6:
20: | React$Element<any>
^^^^^^^^^^^^^^^^^^ type application of type `React$Element`. See lib: /tmp/flow/flowlib_3bead812/react.js:20
Error:
49: {label}
^^^^^ mixed. Inexact type is incompatible with exact type
20: | React$Element<any>
^^^^^^^^^^^^^^^^^^ exact type: object type. See lib: /tmp/flow/flowlib_3bead812/react.js:20
Member 7:
21: | Iterable<React$Node>;
^^^^^^^^^^^^^^^^^^^^ type application of identifier `Iterable`. See lib: /tmp/flow/flowlib_3bead812/react.js:21
Error:
49: {label}
^^^^^ mixed. This type is incompatible with
21: | Iterable<React$Node>;
^^^^^^^^^^^^^^^^^^^^ $Iterable. See lib: /tmp/flow/flowlib_3bead812/react.js:21
Property `@@iterator` is incompatible:
21: | Iterable<React$Node>;
^^^^^^^^^^^^^^^^^^^^ property `@@iterator` of $Iterable. Property not found in. See lib: /tmp/flow/flowlib_3bead812/react.js:21
49: {label}
^^^^^ mixed

Answer Source

Right now, I'm assuming that it is a bug. If you think I'm wrong, and have a better answer, please feel free to answer, and I will accept it gladly!

I assume it is a bug because of the following workaround:

createButtons(): Array<Node> {
  return Object.keys(this.props.values).map((value: string, index: number): Node => (
    <button>
      {this.props.values[value]}
    </button>
  ));
}

By referencing the object directly, instead of iterating over it with Object.entries, flow now assumes the correct type.

EDIT: As I've given this a bit more thought, the problem lies in the typing of arrays, and the fact that arrays in Flow can carry only one type for their elements.

The callback argument of Object.entries() is of type Array<mixed> => void. An ideal type for it would be Array<[K, V]> => void, where K and V are the objects' key and value types, respectively (K is always string, I think.)

This would require heterogeneous collections, where Array<[K, V]> is inhabited by all length-2 arrays whose first element is of type K and the second of type V. This would likely require some type-level operators and higher-kinded types that Flow is (currently) unable to provide.

The workaround using Object.keys works, because Object.keys doesn't need heterogeneous arrays (all keys are of the same type) but it might fail if your object doesn't look like { [string]: X }, but instead has values of different types (say { foo: number, bar: string }, making it (again) a heterogeneous collection.

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download