Advanced static type checking for react using Flow
Motivation
I consider JSX pretty handy but not critical.
What I consider critical instead is the possibility to statically type check as much as I can and currently the possibilities of Flow are kind of limited when it comes to react.
For example there's no way to add constraints to this.props.children
(e.g. The component A
must accept exactly 2 children: the first one must be an instance of component B
, the second one must be an instance of component C
or a string
)
Another example are higher order components, even if it's a popular pattern in the community, currently Flow doesn't play well with HOCs (https://github.com/facebook/flow/issues/1964).
Leaving the comfort of JSX is not a light heartedly decision but it's worth noting this kind of type checking is opt-in, you can mix and match JSX (relatively unsafe) with the vdom builder v
(safer).
Example
Requirement. The Button
component must have a label property and must not have any children
< label: string > { return } const btn = // error: property `label` not found // error// ^^^^^^^ string. This type is incompatible with Children = void
Requirement. The ButtonWrapper
component must not have any properties and must have a single child which must be an instance of the Button
component
<$Shape<{}> Vdom<Button>> { return } // ok // error: property `a` not found // error// ^^^^^^^ string. This type is incompatible with React$ElementButtonWrapper {} btn btn // error// ^^^^^^^^^^ array literal. This type is incompatible with React$Element
Using JSX in the render
method is fine, but calling a component with JSX can be unsafe
<$Shape<{}> Vdom<Button>> { // JSX (unsafe) return <div>thispropschildren</div> } // v(ButtonWrapper, {}, 'hello') // <= using the v builder is safe// ^^^^^^^ string. This type is incompatible with React$Element<ButtonWrapper>hello</ButtonWrapper> // <= this is unsafe, no errors!
Setup
npm install flow-react --save
API
Exports
Component (class)
Component<P: Object = {}, C = void, S = void, D = void>
where
P
= PropsC
= ChildrenS
= StateD
= DefaultProps
v (function)
type Tags = 'a' | 'abbr' | 'address' | /* etc... */ ; v<T: Tags>type: T props?: Object | null children?: any: Vdom<T>v<P C S D T: Component<P C S D>>type: Class<T> props: Object children: C: Vdom<T>
Types
$Strict
Useful to tighten up your props
Example
< a: number > {} // ok // ok <$Strict< a: number >> {} // ok // error
Vdom
Vdom<T>
where
T
= string | your react component
Vdom shortcuts
One for each Tags
member
Example
<{} Div> {} // <= same as Vdom<'div'>// v(A, {}, v('a')) // error
Usage
Note. Add ./node_modules/flow-react/flow-react.js
to your libdefs.
// default for props = {} {}// v(C1, {}, 'hello') // error// v(C1) // error// v(C1, null) // error// v(C1, undefined) // error// const C1$prime: Class<Component<{a: number}>> = C1 // errorconst C1$prime: Class<Component<*>> = C1// v(C1$prime, {}, 'hello') // error <a: number b?: number> {}// v(C1, {a: 1}, 'hello') // error// v(C1) // error// v(C1, null) // error// v(C1, undefined) // error // strictness: use $Shape<P><$Shape<a: number>> {}// v(C3, {a: 1, b: 2}) // error // child<{} string> {}// v(C4, {}, 1) // error // children<{} Array<string>> {}// v(C5, {}, ['a', 1]) // error // tagged children<{} Vdom<'div'>> {}// v(C6, {}, v('a')) // error // function as child<{} number> {}// v(C7, {}, v('div')) // error // component child<{} Vdom<C1>> {}// v(C8, {}, v('div')) // error // default props< a: number b: number void void a: number > static defaultProps = a: 1// v(C9, {}) // error // state< a: number void a: number > { superprops thisstate = a: propsa // this.state = { a: 's' } // error } { this // this.setState({ a: 's' }) // error } // HOC: Class<Component<P2>> { return <P2> { return } } : {a: number} return a: propsblength <a: number> {} // const C12 = hoc(C9, fn) // errorconst C12 =