I have an issue with a flow check on a prop type:
// ButtonInputs.js
export const ButtonTypes = {
PRIMARY: 'primary',
PRIMARY_SPECIAL: 'primary-special',
}
export type Button =
| ButtonTypes.PRIMARY
| ButtonTypes.PRIMARY_SPECIAL;
// MyCustomButton.js
import type { Button } from 'ButtonInputs';
type Props = {
type: Button;
}
<MyCustomButton type="test" /> // WORKS! (shouldn't work)
However, when changing the constants back to normal strings, the flow check works as expected.
export type Button =
| 'primary'
| 'primary-special';
Any idea why this is the case? Is this a known bug?
You can't do this
export type Button =
| ButtonTypes.PRIMARY
| ButtonTypes.PRIMARY_SPECIAL;
Flow raises
src/index.js:11
11: | ButtonTypes.PRIMARY
^^^^^^^^^^^^^^^^^^^ string. Ineligible value used in/as type annotation (did you forget 'typeof'?)
11: | ButtonTypes.PRIMARY
^^^^^^^^^^^^^^^^^^^ PRIMARY
src/index.js:12
12: | ButtonTypes.PRIMARY_SPECIAL;
^^^^^^^^^^^^^^^^^^^^^^^^^^^ string. Ineligible value used in/as type annotation (did you forget 'typeof'?)
12: | ButtonTypes.PRIMARY_SPECIAL;
^^^^^^^^^^^^^^^^^^^^^^^^^^^ PRIMARY_SPECIAL
Even adding typeof
export type Button =
| typeof ButtonTypes.PRIMARY
| typeof ButtonTypes.PRIMARY_SPECIAL;
is not what you want, since typeof ButtonTypes.PRIMARY
is string
, thus <MyCustomButton type="test" />
still type checks
I have the same question. Is this by design and I shouldn't have Enums anymore as long as my magic strings are type checked? Or there's a special way to type check enum values w/ flow?
I guess it supposed to work like this: in the type definitions only actual strings must be used and in the checked code we can use variables / Enums.
The same problem with
const KIND_OBJECT = 1;
const KIND_FIELD = 2;
type FieldKindsT = KIND_OBJECT | KIND_FIELD;
returns error
Ineligible value used in/as type annotation (did you forget 'typeof'?)
KIND_OBJECT: app/_components/Form/schema/formSchema.js:8
But such record works perfectly
type FormSchemaFieldTypesT = 'String' | 'Float' | 'Int' | 'Boolean' | 'Date';
const a: FormSchemaFieldTypesT = 'String'; // no error
const b: FormSchemaFieldTypesT = 'String123'; // expected error "This type is incompatible with string enum"
Also converting kinds to string does not work:
const KIND_OBJECT = '1';
const KIND_FIELD = '2';
type FieldKindsT = KIND_OBJECT | KIND_FIELD;
Finally using now such not perfect, but working solution:
const KIND_OBJECT = 1;
const KIND_FIELD = 2;
type FieldKindsT = 1 | 2;
const a: FieldKindsT = 1; // no error
const b: FieldKindsT = 15; // expected error "This type is incompatible with number enum"
Slightly better:
const KIND_OBJECT: 1 = 1;
const KIND_FIELD: 2 = 2;
type FieldKindsT = typeof KIND_OBJECT | typeof KIND_FIELD;
@vkurchatkin thanks!
A little bit crazy view, but quite applicable.
I ran into a similar issue trying to use imported constants to compute object/type property names.
const Constants = {
A: "asdf",
B: "other"
}
type TwoLiteral = {
asdf: Array<string>,
other: string
}
type TwoCompLiteral = {
[Constants.A]: Array<string>,
[Constants.B]: string
}
const testLiteral: TwoLiteral = {
[Constants.A]: ["hello", "world"],
other: "hello"
};
const testComputed: TwoCompLiteral = {
asdf: ["hello", "world"],
[Constants.B]: "hello"
};
12: [Constants.A]: Array<string>,
^ string. Ineligible value used in/as type annotation (did you forget 'typeof'?)
12: [Constants.A]: Array<string>,
^ A
13: [Constants.B]: string ^ multiple indexers are not supported
23: [Constants.B]: "hello" ^ string. This type is incompatible with
12: [Constants.A]: Array<string>,
^ array type
It's the same problem if I explicitly set the types of the constants to literals. Flow seems to be using the computed property syntax to allow for Map index generation.
any update on the fix?
There is no real bug here. Look at @vkurchatkin's comment for the current best way to do this.
You can also use $Keys
to make this easier when you always have the same values for the keys and values in your object.
export const ButtonTypes = {
PRIMARY: 'PRIMARY',
PRIMARY_SPECIAL: 'PRIMARY_SPECIAL',
}
export type Button = $Keys<typeof ButtonTypes>
I'm trying out Flow and am trying to use it with Redux. I'd like to setup my action creators in a way that flow can verify I've supplied the right type
attribute? For example, if I've setup my flow type as export type SetNotificationAction = TypedAction<'notification:setNotification', string>
I want to only be allowed to return {type: 'notification:setNotification', ...}
export function setNotification(message: string): SetNotificationAction {
return {
type: 'would be nice if any string didn't pass here',
payload: message
};
}
I obviously can't do const SET_NOTIFICATION = 'notification:setNotification'
and export type SetNotificationAction = TypedAction<SET_NOTIFICATION, string>
. I can't use $Keys
because that would mess up my reducer.
For example, it'd be great if Flow could catch this bug (in the reducer CLEAR_NOTIFICATION returns a payload that's undefined and message should always be a string. CLEAR_NOTIFICATION should be SET_NOTIFICATION HERE) :
/* @flow */
export const actionTypes = {
SET_NOTIFICATION: 'notification:setNotification',
CLEAR_NOTIFICATION: 'notification:clearNotification'
};
type TypedAction<T, P> = {
type: T,
payload: P
};
export type SetNotificationAction = TypedAction<'notification:setNotification', string>;
export type ClearNotificationAction = TypedAction<'notification:clearNotification', void>;
export type Action =
| SetNotificationAction
| ClearNotificationAction;
export function clearNotification(): ClearNotificationAction {
return {
type: actionTypes.CLEAR_NOTIFICATION,
payload: undefined
};
}
export function setNotification(message: string): SetNotificationAction {
return {
type: actionTypes.SET_NOTIFICATION,
payload: message
};
}
export type State = { message: string };
const initialState: State = { message: ''};
export default function reducer(state: State = initialState, action: Action): State {
switch (action.type) {
case actionTypes.CLEAR_NOTIFICATION:
return { message: action.payload };
case actionTypes.CLEAR_NOTIFICATION:
return initialState;
default:
return state;
}
}
@nmn That's not really helpful, is it?
Quoting the flowtype docs:
Because Flow understands JavaScript so well, it doesn’t need many of these types. You should only ever have to do a minimal amount of work to describe your code to Flow and it will infer the rest. A lot of the time, Flow can understand your code without any types at all.
minimal amount of work.
I use flowtype because it's helping me write readable code.
This:
export const ButtonTypes = {
PRIMARY: 'PRIMARY',
PRIMARY_SPECIAL: 'PRIMARY_SPECIAL',
}
export type Button = $Keys<typeof ButtonTypes>
Is inferior in every way to this:
const PRIMARY = 1;
const PRIMARY_SPECIAL = 2;
export type Button = PRIMARY | PRIMARY_SPECIAL
or something like this:
const PRIMARY = 1;
const PRIMARY_SPECIAL = 2;
const TYPES = [PRIMARY, PRIMARY_SPECIAL]
export type Button = <unionof TYPES>;
How are people addressing this issue in their code?
What I am currently using (workaround?) is something like this:
// @flow
export const PRIMARY: string & 'primary' = 'primary';
export const SECONDARY: string & 'secondary' = 'secondary';
export type Button = typeof PRIMARY | typeof SECONDARY;
@geraldyeo Thanks, useful trick! It seems to me it can be simplified further:
// @flow
export const PRIMARY: 'primary' = 'primary';
export const SECONDARY: 'secondary' = 'secondary';
export type Button = typeof PRIMARY | typeof SECONDARY;
If you ask me, the official syntax makes more sense:
export const ButtonTypes = {
PRIMARY: 'PRIMARY',
SECONDARY: 'SECONDARY',
PRIMARY_SPECIAL: 'PRIMARY_SPECIAL',
};
type Props = {
type?: $Keys<typeof ButtonTypes>,
};
export default ({ type = ButtonTypes.SECONDARY, ...otherProps }: Props) => (
<button type={type} {...otherProps} />
);
This way in an external file you can do the following:
import Button, { ButtonTypes } from 'components/Button';
export default() => (
<Button type={ButtonTypes.PRIMARY)>
Button label
</Button>
);
@nmn there are really common use cases that would benefit from allowing this kind of syntax in a compact way. typing actions and reducers inside redux applications would become a lot nicer and safer for instance
This is what we've come up with:
const ANIMAL = {
DOG: ('dog': 'dog'),
CAT: ('cat': 'cat'),
};
type Animal = $Values<typeof ANIMAL>;
const success: Animal = ANIMAL.DOG;
const failure: Animal = 'hog';
Thanks to @fagerbua , I improved the code just like this
// @flow
const ButtonTypes:
{
PRIMARY: 0,
SECONDARY: 1,
PRIMARY_SPECIAL: 2,
} = {
PRIMARY: 0,
SECONDARY: 1,
PRIMARY_SPECIAL: 2,
}
type Props = $Values<typeof ButtonTypes>;
function show(buttonType: Props) {
console.log(buttonType);
}
show(ButtonTypes.PRIMARY); // No Error
show(3); // This type is incompatible with the expected param type of number enum
@Naoto-Ida, @ckitterl: Using $Keys
gives much simpler error messages, see flow.org/try
@guidobouman You may misunderstand the $keys usage。 The $Keys
// @flow
const countries = {
US: "United States",
IT: "Italy",
FR: "France"
};
type Country = $Keys<typeof countries>;
const italy: Country = 'IT';
const nope: Country = 'nope'; // 'nope' is not a Country
Country type is the union of US
,IT
ANDFR
。My last example show how to get a Country type that combine United States
, Italy
AND France
The ButtonTypes of Your example by coincidence have same key and value。 see try it
@ckitterl I'm well aware of that fact. To me duplication of the whole list of types really feels like a no go. I'd much rather use keys with the same values. But this is just personal preference.
What would actually improve this situation would be an $ExactValues
helper. This would allow using the exact values instead of value types.
Proposed:
// @flow
const countries = {
US: "United States",
IT: "Italy",
FR: "France"
};
type Country = $ExactValues<typeof countries>;
const italy1: Country = countries.IT; // This would work
const italy2: Country = 'Italy'; // Ideally this would fail, but that's out of scope
const nope: Country = 'nope'; // 'nope' is not a Country
@guidobouman It works actually. You just need to freeze the object (or pretend you freeze it)
https://flow.org/try/#0PTAEAEDMBsHsHcBQiDGsB2BnALqNBXdbAJwEsBTTUAXlGACoAuRgeQCMArclbAOkmLlyAL3IAKesADeiUKACqAZUagARPPSls5ACahF2AIbbMqgDSzQASQAqK1VaPQAnucsAxAEr33xQ+hRyVUQAXwZmAEpJAG5kbGcAB3JQAGFYQhJnGlAAEgA1Q2h8SgAeeKTYSDx0ojJKAD5Y1AwcUC1C5wBGFTSM4izaAlqKTF5baLowGwALUip4dOg9BeIAa2asXHaXACYemszsgHJHDqOJkGsdckKXUGxZ+cW9SENSaDNQNnxcB+MjqjpXCVUCYNBJDatdCwJL7PoDUBHaFJc6TRHI8hHNpUaG4QypA79IA
Oh, that's more like it! 👏
PS: On a lower level this might get fixed in a different way: https://github.com/facebook/flow/issues/2639
@TrySound It's work under version of 0.6x.y but not 0.5x.y. I run my script by 0.59.0, but, thank you all the samle ,I will td my flow
Most helpful comment
@nmn That's not really helpful, is it?
Quoting the flowtype docs:
minimal amount of work.
I use flowtype because it's helping me write readable code.
This:
Is inferior in every way to this:
or something like this: