Marcus Junius Brutus Marcus Junius Brutus - 21 days ago 7
React JSX Question

Flow requiring annotation on object literal

Assuming that

cx
is defined as:

var cx = require('classnames');


… I have the following JSX code:

test: function(panelEnabled: boolean) {
const klass = cx({disabled: !panelEnabled});
return (<div className={klass}>foo</div>);
},


The above results in Flow 0.35.0 complaining with the following message:

app/components/target-resolution.js:20
20: const klass = cx({disabled: !panelEnabled});
^^^^^^^^^^^^^^^^^^^^^^^^^ object literal. Could not decide which case to select
17: ...classes: Array<$npm$classnames$Classes>
^^^^^^^^^^^^^^^^^^^^^^^ union type. See lib: flow-typed/npm/classnames_v2.x.x.js:17
Case 2 may work:
8: {[className: string]: boolean } |
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ object type. See lib: flow-typed/npm/classnames_v2.x.x.js:8
But if it doesn't, case 3 looks promising too:
9: {[className: string]: ?boolean } |
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ object type. See lib: flow-typed/npm/classnames_v2.x.x.js:9
Please provide additional annotation(s) to determine whether case 2 works (or consider merging it with case 3):
20: const klass = cx({disabled: !panelEnabled});
^^^^^^^^^^^^^ not operator


I can work around this issue with either of the below two approaches:

ugly but doesn't change the API of the method



test: function(panelEnabled: boolean) {
const klassSpec: {disabled: boolean} ={disabled: !panelEnabled};
const klass = cx(klassSpec);
return (<div className={klass}>foo</div>);
},


shorter but requires changing the API



test: function(panelDisabled: boolean) {
const klass = cx({disabled: panelDisabled});
return (<div className={klass}>foo</div>);
},


I have gotten the libdefs from flow-typed and the file I am pointed to by the flow message (
flow-typed/npm/classnames_v2.x.x.js
) is a mere 17 lines:

// flow-typed signature: 2dfd96b054f56a84f2d08769019d32d7
// flow-typed version: dc0ded3d57/classnames_v2.x.x/flow_>=v0.23.x_<=v0.27.x

type $npm$classnames$Classes =
string |
// We need both of these because if we just have the latter it won't accept objects typed
// explicitly as the former, due to mutation concerns.
{[className: string]: boolean } |
{[className: string]: ?boolean } |
Array<string> |
false |
void |
null

declare module 'classnames' {
declare function exports(
...classes: Array<$npm$classnames$Classes>
): string;
}


My questions are:


  1. what is the cause and significance of this message ?

  2. what is the proper way to handle this?

  3. why isn't it clear to Flow that if
    panelEnabled
    is
    boolean
    then
    !panelEnabled
    is also boolean.


Answer

It seems that you have installed the wrong version of the classnames definition file.

If you see the commented line in the definition file

// flow-typed version: dc0ded3d57/classnames_v2.x.x/flow_>=v0.23.x_<=v0.27.x

it indicates that this definition file is compatible with flow >= 0.23 up to flow 0.27.

Maybe you're using an out-dated version of flow-typed? Try updating it with npm install -g flow-typed.

I bootstrapped a project from scratch to test your code and it works correctly. Here's my package.json:

{
  "name": "flow-classnames",
  "scripts": { "flow": "flow" },
  "dependencies": {
    "classnames": "^2.2.5"
  },
  "devDependencies": {
    "flow-bin": "^0.35.0"
  }
}

and here's my index.js

// @flow

const cx = require('classnames');

const test = function(panelEnabled: boolean) {
  const klass = cx({disabled: !panelEnabled});
  return (<div className={klass}>foo</div>);
}

Steps to test:

npm install
flow-typed install
npm run flow

No errors!