Make the type the values of an object in flow - flowtype

I have a file that exports a static object of key/value pairs:
const.js:
// #flow
export default const DEFAULTS = {
myDefault: 'a',
myDefaultB: 'b',
}
I'd like to say that the type of an object in another file is one of the values in DEFAULTS
main.js:
// #flow
import DEFAULTS from './const';
type MyReturnType = {|
defaultValue = 'a' | 'b',
|}
Looking through the flow docs it seems like $Values is what I want - however,
type MyReturnType = {|
defaultValue = $Values<DEFAULTS>,
|}
throws a flow error. If I prefix it with typeof, it works, though.
type MyReturnType = {|
defaultValue = $Values<typeof DEFAULTS>,
|}
Is $Values correct? It seems to me like thats just saying it's a string, not necessarily limiting it to just 'a' or 'b'.

In order to $Values to resolve literal types (e.g. 'a' | 'b' instead of just string) it should be applied to frozen object:
const DEFAULTS = Object.freeze({
a: 'a',
b: 'b'
});
type DefaultValues = $Values<typeof DEFAULTS>;
const foo: DefaultValues = 'a'; // OK
const bar: DefaultValues = 'unexpected'; // string is incompatible with enum
Try

Related

flow returns wrong type

I have this piece of code:
/* #flow */
import { List } from 'immutable';
type NotMapped = {|
first: Array<number>,
second: number,
|};
type Mapped = {|
first: List<number>,
second: number,
|};
const notMapped: NotMapped = {
first: [1, 2, 3],
second: 10,
};
const map = () => {
const first = List(notMapped.first);
return { ...notMapped, first };
}
const result: Mapped = map();
I want result to beMapped type but I get:
{|
first: List<number> | Array<number>,
second: number
|}
How come that flow thinks it might be Array<number> when I explicitly set first as List(notMapped.first)? It only works if I set return { second: notMapped.second, first };, but this isn't a solution because I have large amount of data and I cannot set every item.
I have checked and this is well-known issue, but when spreading types, not object with assigned type. Is there any solution for this?

Cannot assign object literal to `...` because property `...` is missing in object literal [1] but exists in `...`

I am new flow and currently working on a project with flow strict. I have a type alias being passed to a class. The snippet can be found here
// #flow strict
export type CustomType = {
a: string,
b: boolean,
c: boolean,
d: boolean,
e: boolean
};
let d = true;
let b = true;
let customType:CustomType = {
d,
b,
c: true,
}
class Custom{
constructor(customType = {}) {}
}
let custom = new Custom(customType);
When passing the object not all properties customType are present. What is the best fix here?
You could type the keys of the CustomType object as optional:
(Try)
// #flow strict
export type CustomType = {
a?: string,
b?: boolean,
c?: boolean,
d?: boolean,
e?: boolean
};
let d = true;
let b = true;
let customType: CustomType = {
d,
b,
c: true,
}
class Custom{
constructor(customType: CustomType = {}) {}
}
let custom = new Custom(customType);
Alternatively, you could use $Shape<T>. It lets you only pass the keys you're interested in:
(Try)
// #flow strict
export type CustomType = {
a: string,
b: boolean,
c: boolean,
d: boolean,
e: boolean
};
let d = true;
let b = true;
let customType: $Shape<CustomType> = {
d,
b,
c: true,
}
class Custom{
constructor(customType: $Shape<CustomType> = {}) {}
}
let custom = new Custom(customType);

Optional group of keys

I have to make two keys optional index and remove in an object. However if one is provided the other must be there. So its like this:
type Props = {
isSettings: boolean,
} | {
index: number,
remove: $PropertyType<FieldProps, 'remove'> // (index: number) => void
}
Where the second object is optional. The above is not working, as it is not expecting isSettings in the 3rd object. But it is always required.
Standard object types in Flowtype are objects defined to have at least the properties specified. This means that if you have a type like { isSettings: boolean } you are saying only that that the object has an isSettings property that is a boolean. It is allowed to have other properties, it just has to know the type of isSettings.
This means that if you have a type
type Props = {
isSettings: boolean,
} | {
index: number,
remove: (index: number) => void
};
then doing
var obj: Props = ...
if (obj.remove) {
var n: number = obj.index;
}
will fail, because it doesn't prove anything, because you have not prohibited there being a remove property on both objects.
In order to refine an object type like this one, Flow needs to be told that each type has exactly the given set of properties. This is where Flow's Exact object types come in.
If you change your types to be
type Props = {|
isSettings: boolean,
|} | {|
index: number,
remove: (index: number) => void
|};
then a snippet like
var obj: Props = ...
if (obj.remove) {
var n: number = obj.index;
}
will work as expected, because the presence of remove means there must be a property called index that is a number.

How to get a function's return type in flow?

With this example:
const myObj = {
test: true,
};
type MyType = typeof myObj;
const getValue = (): MyType => {
return myObj;
};
// how to do this??
type TheReturnType = getValue;
const nextObj: TheReturnType = {
test: false,
};
I'd like to extract the type that the function will return, so I can reuse that type. I can think of no way to get it. The above doesn't work. typeof getValue will return the function.
Flow has a $Call utility type, which can get a function's return type:
type TheReturnType = $Call<typeof getValue>
However, if your function takes arguments, you need to provide types for those as well:
type TimeoutType = $Call<typeof setTimeout, () => void, number>
If that seems inconvenient, you can write a ReturnType helper that can skip the need for arguments:
type ReturnType<F> =
$PropertyType<$ObjMap<{ x: F }, <R>(f: (...any) => R) => R>, 'x'>
Let's use this:
type TheReturnType = ReturnType<typeof setTimeout>
This ReturnType helper basically matches the ReturnType helper present in TypeScript.

Why doesn't Flow see members of objects in this polymorphic union type?

The Setup
Here's a complete try flow example illustrating the issue.
Types
export type ActionT<TT: string, PT> = {|
+type: TT,
+payload: PT,
+error?: boolean,
+meta?: any
|}
export type ChangePayloadT = {
+_change: {|
+state: 'PENDING' | 'FULFILLED' | 'REJECTED',
+id: string,
+error?: any,
+message?: string,
|}
}
export type IdPayloadT = {
id: string,
}
type PayloadT = IdPayloadT | ChangePayloadT
type MyActionT = ActionT<'SET' | 'MERGE', PayloadT>
As you can see, MyActionT can contain a payload with either an id or a _change object. It's not quite (?) a disjoint union because there isn't a single property to disambiguate on.
This seems like it should work, but doesn't:
function lookup3 (action: MyActionT): any {
if (action.payload._change) {
// why does this error?
return action.payload._change.id
} else {
return action.payload.id
}
}
Anyone care to set me straight as to why?
Ok, so the solution apparently involved making the the two types a proper disjoint union:
export type ChangePayloadT = {
+_change: {|
+state: $Keys<typeof asyncStates>,
+id: string,
+error?: any,
+message?: string,
|},
id?: string,
}
export type IdPayloadT = {
+_change?: void,
+id: string,
}
With the second type now having an explicitly void _change, flow knows to tell the types apart based on the presence or absence of a _change.
Working tryflow Yay. :)

Resources