Multiple indexers not allowed error, but they are not indexers - flowtype

My goal is to create an Entities type, which is an "exact object type" of all the keys in K.
K is:
type SocialEntity = Article | Thumb | Comment | Displayname | Helpful;
type SocialEntityKind = 'articles' | 'thumbs' | 'comments' | 'displaynames' | 'helpfuls';
const K: {[key: SocialEntityKind]: SocialEntityKind} = { // short for SOCIAL_ENTITY_KIND
articles: 'articles',
thumbs: 'thumbs',
comments: 'comments',
displaynames: 'displaynames',
helpfuls: 'helpfuls'
}
Now I tried setting [key: typeof K.*] but its considering these indexers.
type Entities = {|
[key: typeof K.articles]: {
[key: ArticleId]: Article
},
[key: typeof K.thumbs]: {
[key: ThumbId]: Thumb
},
[key: typeof K.comments]: {
[key: CommentId]: Comment
},
[key: typeof K.displaynames]: {
[key: DisplaynameId]: Displayname
},
[key: typeof K.helpfuls]: {
[key: HelpfulId]: Helpful
}
|}
Here is a screenshot of the error:
Is there any easy and right way to do this?

That syntax will not work for what you are trying to do. [key: TYPE]: TYPE in Flowtype is syntax for using objects as dictionaries, not syntax for computed property names.
It is also not possible to use typeof in the way you want, because the type is just string, not articles exactly. You'll have to manually put in the names of the keys.

Related

How to get CSS types for React / styled-components?

I am getting this error in my styled component:
Type 'string | undefined' is not assignable to type 'WordBreak | undefined'.
It is happening here:
type PropsType = {
breakEverywhere?: boolean
breakWord?: boolean
}
const Text = styled.p<PropsType>(props => ({
wordBreak: getWordBreak(props),
}))
function getWordBreak(props: PropsType): string | undefined {
if (props.breakWord) {
return 'break-word'
}
if (props.breakEverywhere) {
return 'break-all'
}
}
It can be easily fixed by leaving off the type annotation string | undefined on the getWordBreak function. But how can I add a type annotation? It says WordBreak, but google searching for WordBreak type definitions doesn't yield any results, and no VSCode help. Any ideas?
The same sort of problem happens if I abstract away textAlign to in a similar way, it talks about TextAlign type. Looks like the csstype won't help either.
If I use this in the styled component:
textAlign: props.align ? TEXT_ALIGN[props.align] : undefined,
And I have this:
type AlignType = 'center' | 'end' | 'start'
const TEXT_ALIGN: Record<AlignType, string> = {
center: 'center',
end: 'right',
start: 'left',
}
Then i get this:
Types of property 'textAlign' are incompatible.
Type 'string | undefined' is not assignable to type 'TextAlign | undefined'.
Type 'string' is not assignable to type 'TextAlign | undefined'.ts(2345)
I can fix it with an untyped function instead:
function getTextAlign(align: AlignType) {
switch (align) {
case 'center':
return 'center'
case 'end':
return 'right'
default:
return 'left'
}
}
But that is ugly, how can I do it the Record way or a cleaner way? How can I get access to these types?
Looks like the csstype won't help either.
Styled-components types are based on csstype, so you should be able to get what you need from there.
Type WordBreak is in namespace Property of csstype:
export namespace Property {
// ...
export type WordBreak = Globals | "break-all" | "break-word" | "keep-all" | "normal";
// ...
}
Using it with your code sample:
import styled from "styled-components"
import { Property } from "csstype"
const Text2 = styled.p<PropsType>(props => ({
wordBreak: getWordBreak2(props), // Okay
}))
function getWordBreak2(props: PropsType): Property.WordBreak | undefined { // Okay
if (props.breakWord) {
return 'break-word'
}
if (props.breakEverywhere) {
return 'break-all'
}
return
}
Playground Link

Make the type the values of an object in flow

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

I don't understand incompatible type error in assignment

Can someone explain why I have the flow error
object type (This type is incompatible with object type Indexable signature is incompatible:)
for the assignment in the last line of
const plain: { [key: string]: string } = { prop: '' };
type TestType = { [key: string]: string | number };
const testVar: TestType = plain;
I have no error if I remove the type specification for plain...
Many thanks !
Vladimir Kurchatkin answered to this question in the issue I opened on github (https://github.com/facebook/flow/issues/5458) :
Objects are invariant by default. You can make it covariant like this:
const plain: { [key: string]: string } = { prop: '' };
type TestType = { +[key: string]: string | number };
const testVar: TestType = plain;

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.

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