Utility Types

Creating complex types with utility types

$Keys<T>

In Flow you can use union types similar to enums:

1
2
3
4
5
// @flow
type Suit = "Diamonds" | "Clubs" | "Hearts" | "Spades";

const clubs: Suit = 'Clubs';
const wrong: Suit = 'wrong'; // 'wrong' is not a Suit
string This type is incompatible with string enum

This is very handy, but sometimes you need to access the enum definition at runtime (i.e. at a value level).

Suppose for example that you want to associate a value to each suit of the previous example.

You could do

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// @flow
type Suit = "Diamonds" | "Clubs" | "Hearts" | "Spades";

const suitNumbers = {
  Diamonds: 1,
  Clubs: 2,
  Hearts: 3,
  Spades: 4
};

function printSuitNumber(suit: Suit) {
  console.log(suitNumbers[suit]);
}

printSuitNumber('Diamonds'); // 2
printSuitNumber('foo'); // 'foo' is not a Suit
string This type is incompatible with the expected param type of string enum

but this doesn’t feel very DRY, as we had to explicitly define the suit names twice.

In situations like this one, you can leverage the $Keys<T> operator. Let’s see another example, this time using $Keys:

1
2
3
4
5
6
7
8
9
10
11
// @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
property `nope` Property not found in object literal

In the example above, the type of Country is equivalent to type Country = 'US' | 'IT' | 'FR', but Flow was able to extract it from the keys of countries.

$Diff<A, B>

As the name hints, $Diff<A, B> is the type representing the set difference of A and B, i.e. A \ B, where A and B are both Object Types. Here’s an example:

1
2
3
4
5
6
7
8
9
10
11
12
// @flow
type Props = { name: string, age: number };
type DefaultProps = { age: number };
type RequiredProps = $Diff<Props, DefaultProps>;

function setProps(props: RequiredProps) {
  // ...
}

setProps({ name: 'foo' });
setProps({ name: 'foo', age: 42, baz: false }); // you can pass extra props too
setProps({ age: 42 }); // error, name is required
property `name` Property not found in object literal

As you may have noticed, the example is not a random one. $Diff is exactly what the React definition file uses to define the type of the props accepted by a React Component.

Class<T>

Given a type T representing instances of a class C, the type Class<T> is the type of the class C. For example:

1
2
3
4
5
6
7
8
9
10
11
12
13
// @flow
class Store {}
class ExtendedStore extends Store {}
class Model {}

function makeStore(storeClass: Class<Store>) {
  return new storeClass();
}

(makeStore(Store): Store);
(makeStore(ExtendedStore): Store);
(makeStore(Model): Model); // error
(makeStore(ExtendedStore): Model); // Flow infers the return type
Store This type is incompatible with Model Model This type is incompatible with the expected param type of Store Store This type is incompatible with Model

For classes that take type parameters, you must also provide the parameter. For example:

1
2
3
4
5
6
7
8
9
10
// @flow
class ParamStore<T> {
  constructor(data: T) {}
}

function makeParamStore<T>(storeClass: Class<ParamStore<T>>, data: T): ParamStore<T> {
  return new storeClass(data);
}
(makeParamStore(ParamStore, 1): ParamStore<number>);
(makeParamStore(ParamStore, 1): ParamStore<boolean>); // failed because of the second parameter
number This type is incompatible with boolean

$Supertype<T>

Work in progress

$Subtype<T>

Work in progress

$Abstract<T>

Work in progress

$PropertyType<T, x>

A $PropertyType is the type at a given key.

As of Flow v0.36.0, x must be a literal string. In future versions, x may be allowed to be any type, as long as that type exists on the keys of T.

1
2
3
4
5
6
7
8
9
10
// @flow
type Person = {
  name: string,
  age: number,
  parent: Person
};

const newName: $PropertyType<Person, 'name'> = 'Michael Jackson';
const newAge: $PropertyType<Person, 'age'> = 50;
const newParent: $PropertyType<Person, 'parent'> = 'Joe Jackson';
string This type is incompatible with object type

This can be especially useful for referring to the type of React props, or, even the entire props type itself.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// @flow
import React from 'react';
class Tooltip extends React.Component {
  props: {
    text: string,
    onMouseOver: ({x: number, y: number}) => void
  };
}

const someProps: $PropertyType<Tooltip, 'props'> = {
  text: 'foo',
  onMouseOver: (data: {x: number, y: number}) => undefined
};

const otherProps: $PropertyType<Tooltip, 'props'> = {
  text: 'foo'
  // Missing the `onMouseOver` definition
};
property `Component` Too few type arguments. Expected at least 1 See type parameters of definition here property `onMouseOver` Property not found in object literal

You can even nest lookups:

1
2
3
4
// @flow
type PositionHandler = $PropertyType<$PropertyType<Tooltip, 'props'>, 'onMouseOver'>;
const handler: PositionHandler = (data: {x: number, y: number}) => undefined;
const handler2: PositionHandler = (data: string) => undefined; // wrong parameter types
identifier `Tooltip` Could not resolve name

You can use this in combination with Class<T> to get static props:

1
2
3
4
5
6
7
8
// @flow
class BackboneModel {
  static idAttribute: string | false;
}

type ID = $PropertyType<Class<BackboneModel>, 'idAttribute'>;
const someID: ID = '1234';
const someBadID: ID = true;
boolean This type is incompatible with union: string | boolean literal `false`

The Existential Type (*)

* is known as the existential type.

An existential type is used as a placeholder to tell Flow to infer the type.

For example, in the Class<ParamStore<T>> example, we could have used an existential type for the return:

1
2
3
4
5
6
// @flow
function makeParamStore<T>(storeClass: Class<ParamStore<T>>, data: T): * {
  return new storeClass(data);
}
(makeParamStore(ParamStore, 1): ParamStore<number>);
(makeParamStore(ParamStore, 1): ParamStore<boolean>); // failed because of the second parameter
identifier `ParamStore` Could not resolve name

The * can be thought of as an “auto” instruction to Flow, telling it to fill in the type from context.

In comparison to any, * may allow you to avoid losing type safety.

The existential operator is also useful for automatically filling in types without unnecessary verbosity:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// @flow
class DataStore {
  data: *; // If this property weren't defined, you'd get an error just trying to assign `data`
  constructor() {
    this.data = {
      name: 'DataStore',
      isOffline: true
    };
  }
  goOnline() {
    this.data.isOffline = false;
  }
  changeName() {
    this.data.isOffline = 'SomeStore'; // oops, wrong key!
  }
}
string This type is incompatible with boolean

$Exact<T>

$Exact<{name: string}> is a synonym for {| name: string |} as in the Object documentation.

1
2
3
4
5
6
7
8
// @flow
type ExactUser = $Exact<{name: string}>;
type ExactUserShorthand = {| name: string |};

const user2 = {name: 'John Wilkes Booth'};
// These will both be satisfied because they are equivalent
(user2: ExactUser);
(user2: ExactUserShorthand);

$ObjMap<T, F>

ObjMap<T, F> is the type obtained by taking the type of the values of an object and mapping them with a type function.

Warning

$ObjMap is currently in an unstable state, due to a bug that allows invalid writes.

For more information, please refer to #2674.

Let’s see an example. Suppose you have a function called run that takes an object of thunks (functions in the form () => A) in input:

1
2
3
4
// @flow
function run<A, O: {[key: string]: () => A}>(o: O) {
  return Object.keys(o).reduce((acc, k) => Object.assign(acc, { [k]: o[k]() }), {});
}

The function’s purpose is to run all the thunks and return an object made of values. What’s the return type of this function?

The keys are the same, but the values have a different type, namely the return type of each function. At a value level (the implementation of the function) we’re essentially mapping over the object to produce new values for the keys. How to express this at a type level?

This is where ObjMap<T, F> comes in handy.

1
2
3
4
5
6
7
8
// @flow

// let's write a typelevel function that takes a `() => V` and returns a `V` (its return type)
type ExtractReturnType = <V>(() => V) => V 

function run<A, O: {[key: string]: () => A}>(o: O): $ObjMap<O, ExtractReturnType> {
  return Object.keys(o).reduce((acc, k) => Object.assign(acc, { [k]: o[k]() }), {});
}

Let’s try this out

1
2
3
4
5
6
7
8
9
10
11
12
// @flow
const o = {
  a: () => true,
  b: () => 'foo'
};
      
(run(o).a: boolean); // Ok
(run(o).b: string);  // Ok
// $ExpectError
(run(o).b: boolean); // Nope, b is a string
// $ExpectError
run(o).c;            // Nope, c was not in the original object
identifier `run` Could not resolve name

This is extremely useful for expressing the return type of functions that manipulate objects values. You could use a similar approach (for instance) to provide the return type of bluebird’s Promise.props function, which is like Promise.all but takes an object as input.

Here’s a possible declaration of this function, which is very similar to our first example:

1
2
// @flow
declare function props<A, O: { [key: string]: A }>(promises: O): Promise<$ObjMap<O, typeof $await>>;

And use:

1
2
3
4
5
6
7
// @flow
const promises = { a: Promise.resolve(42) };
props(promises).then(o => {
  (o.a: 42); // Ok
  // $ExpectError
  (o.a: 43); // Error, flow knows it's 42
});
identifier `props` Could not resolve name

Was this guide helpful? Let us know by sending a message to @flowtype.