Inexact vs exact flow object types: spreading

Reading Time: 3 minutes

If one wants to add flow annotation to object type and reuse some of the types, that was annotated in another object type, it can be done like:

type SharedProperties = {
length: number,
name: string,
};
type NewProperties = {
age: number,
...SharedProperties,
};

However, in this case, not all will run smoothly here, as if to check age will accept, not only number type as valid but other as well.

Open Flow Try playground

If to annotate objects, as it is done above the age type will become mixed. This has happened because SharedProperties has been annotated as an inexact object, which means, that if it doesn’t explicitly have age property type, it still can have this property, but is not defined in the annotation of the object:

Open Flow Try playground

There are a few ways how to annotate NewProperties to obtain the expected type (means only age that has number type would be accepted).

1) Spread SharedProperties type first and then add the annotation to age. In this case, even though age was defined as mixed (no explicitly) in the SharedProperties, its type will be redefined by further age annotation.

Open Flow Try playground

This will work, but it will be not resistant for the annotation changes as if for some reason someone will in the future change the order of destructuring SharedProperties and other properties definition, an error will appear one more time, and it will be an error that is not easy to track.

One more downside of this approach, that is not easy to spot, is that spreading inexact type will make all its parameter be optional, what is not obvious if one doesn’t know that:

Open Flow Try playground

2) Add all shared properties annotation in NewProperties one by one using $PropertyType.

Open Flow Try playground

Con of this approach is the case when SharedProperties contains a lot of properties. Then manually adding every prop in the NewProperties definition will diminish most of the pros that we have from having properties types defined in the SharedProperties in the first place. However, if we would want to obtain annotation only from some props from SharedProperties (not all) this method will allow us to do this. Note also, that in this case NewProperties has both name and length properties as required ones.

3) Change SharedProperties to be Exact type. Semantically there are two ways how to make Object annotation to be the exact type (there is no difference between them).

type SharedPropertiesExact1 = {|
length: number,
name: string,
|};
type SharedPropertiesExact2 = $Exact<{
length: number,
name: string,
}>;

Then our case will look like:

Open Flow Try playground

From my perspective, this solution has the most benefits, as it keeps all the pros that destructuring of the SharedProperties is bringing in the first places, at the same time it is more resistant to the order in which destructuring and other properties are added in the NewProperties annotation. Similarly to the previous approach, in this case, both name and length properties as mandatory ones.

I have created the eslint rule, that helps to enforce the third option across the project (check for the rule spread-exact-type in the eslint-plugin-flowtype set of rules – https://github.com/gajus/eslint-plugin-flowtype#eslint-plugin-flowtype-rules-spread-exact-type). Unfortunately, there is no easy way to detect is the original type exact at the moment when it is created, but this rule allows to check, that $Exact utility type is added whenever spreading occurs.

Recently my general logic with flow types is that everything should be annotated as exact as possible, if something is annotated not exact, this should be done with the reason under it. Flow maintainers themselves are identifying that migration for Object type to be exact by default is in their roadmap (check this post for more details https://medium.com/flow-type/on-the-roadmap-exact-objects-by-default-16b72933c5cf).

If you have found a spelling error, please, notify us by selecting that text and pressing Ctrl+Enter.

Olena Sovyn
Staff Software Engineer (London, UK). I ❤ React, Redux, lodash, React Storybook, and functional programming overall. Always open to learning something new

1 Comment

Leave a Comment

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Spelling error report

The following text will be sent to our editors: