After Flow 0.85, Flow starts Asking for Required Annotations on implicit calls of higher order components within each file import — export cycle. This facilitates Flow to merge type information from file dependencies before it walks the type structure and conducts type inference.
This helps Flow gain significantly better coverage on higher order components. But it also asks that we explicitly annotate the connected components at file exports. Exporting implicit calls of connect
will raise error:
Missing type annotation for OP. OP is a type parameter declared in function type [1] and was implicitly
instantiated at call of connect [2].
In general, to make Flow happy with connect after 0.85 is a two-phase fix. First, you need to explicitly annotate each connected components at file exports. This shall clear all the “implicitly instantiated” errors. Then, if your codebase contains mismatched types between component definitions and usages, Flow will report those errors after you fix the implicit instantiation errors.
Fixing the “implicitly instantiated” errors at calls of connect
Note: We need
React.AbstractComponent
from Flow v0.89+
Annotating at function return
The easiest way to annotate connected components is to annotate at function call return. To do this, we need to know to types of props in our components:
-
OwnProps
: likely contain or equal to what you need as the second parameter tomapStateToProps
. If there are props that are not used bymapStateToProps
, i.e., the props that "pass through", include them here inOwnProps
as well -
Props
:OwnProps
plus the props passed in bymapStateToProps
andmapDispatchToProps
Note: Inexact objects don't spread nor
$Diff
very well. It is strongly recommended that you use exact objects for connected components all the time.
type OwnProps = {|
passthrough: string,
forMapStateToProps: string
|};
type Props = {|
...OwnProps,
fromMapStateToProps: string,
dispatch1: () => void
|};
With OwnProps
and Props
in figured out, we are now ready to annotate the connected components.
In component definition, annotate the props with Props
. The component will have access to all the injected props from connect
:
import * as React from "react";
const MyComponent = (props: Props) => (
<div onClick={props.dispatch1}>
{props.passthrough}
{props.fromMapStateToProps}
</div>
);
When we export, this is also when we normally call connect
, annotate the exported component with just OwnProps
:
import * as React from "react";
// const MyComponent = ...
export default (connect(
mapStateToProps,
mapDispatchToProps
)(MyComponent): React.AbstractComponent<OwnProps>);
Annotating by providing explicit type parameters
We may also annotate connected components by providing explicit type parameters at call of connect
with the help of the newest Flow Typed library definition for React Redux. Note that this will also require Flow v0.89+.
The Flow Typed library definition declares connect
as follows:
declare export function connect<-P, -OP, -SP, -DP, -S, -D>(
mapStateToProps?: null | void,
mapDispatchToProps?: null | void,
mergeProps?: null | void,
options?: ?Options<S, OP, {||}, MergeOP<OP, D>>
): Connector<P, OP, MergeOP<OP, D>>;
The libdef also contains a glossary of the abbreviations which decrypts the signature to:
connect<Props, OwnProps, StateProps, DispatchProps, State, Dispatch>(…)
For the most common ways to connect components, we won't need all of the parameters. Normally, we need only OwnProps
and Props
at the call of connect
, and State
at the definition of mapStateToProps
.
We may use _
(what's this?) as placeholder at unused type parameter positions. A common connect
call may look like this:
connect<Props, OwnProps, _, _, _, _>(…)
We include examples for three major use cases of annotating connect
with Flow:
- Connecting stateless component with
mapStateToProps
- Connecting components with
mapDispatchToProps
of action creators - Connecting components with
mapStateToProps
andmapDispatchToProps
of action creators
Connecting stateless component with mapStateToProps
type OwnProps = {|
passthrough: number,
forMapStateToProps: string,
|};
type Props = {|
...OwnProps,
fromStateToProps: string
|};
const Com = (props: Props) => <div>{props.passthrough} {props.fromStateToProps}</div>
type State = {a: number};
const mapStateToProps = (state: State, props: OwnProps) => {
return {
fromStateToProps: 'str' + state.a
}
};
const Connected = connect<Props, OwnProps, _, _, _, _>(mapStateToProps)(Com);
Connecting components with mapDispatchToProps
of action creators
type OwnProps = {|
passthrough: number,
|};
type Props = {|
...OwnProps,
dispatch1: (num: number) => void,
dispatch2: () => void
|};
class Com extends React.Component<Props> {
render() {
return <div>{this.props.passthrough}</div>;
}
}
const mapDispatchToProps = {
dispatch1: (num: number) => {},
dispatch2: () => {}
};
const Connected = connect<Props, OwnProps, _, _, _, _>(null, mapDispatchToProps)(Com);
e.push(Connected);
<Connected passthrough={123} />;
Connecting components with mapStateToProps
and mapDispatchToProps
of action creators
type OwnProps = {|
passthrough: number,
forMapStateToProps: string
|};
type Props = {|
...OwnProps,
dispatch1: () => void,
dispatch2: () => void,
fromMapStateToProps: number
|};
class Com extends React.Component<Props> {
render() {
return <div>{this.props.passthrough}</div>;
}
}
type State = {a: number}
type MapStateToPropsProps = {forMapStateToProps: string}
const mapStateToProps = (state: State, props: MapStateToPropsProps) => {
return {
fromMapStateToProps: state.a
}
}
const mapDispatchToProps = {
dispatch1: () => {},
dispatch2: () => {}
};
const Connected = connect<Props, OwnProps, _, _, _, _>(mapStateToProps, mapDispatchToProps)(Com);
Annotating nested higher order components with connect
If you are at the unfortunate position where your component is wrapped with nested higher order component, it is probably more difficult to annotate by providing explicit type parameters, as doing so will probably require that you tediously take away props at each layer. It is agian easier to annotate at function return:
type OwnProps = {|
passthrough: number,
forMapStateToProps: string,
|}
type Props = {|
...OwnProps,
injectedA: string,
injectedB: string,
fromMapStateToProps: string,
dispatch1: (number) => void,
dispatch2: () => void,
|}
const Component = (props: Props) => { // annotate the component with all props including injected props
/** ... */
}
const mapStateToProps = (state: State, ownProps: OwnProps) => {
return { fromMapStateToProps: 'str' + ownProps.forMapStateToProps },
}
const mapDispatchToProps = {
dispatch1: number => {},
dispatch2: () => {},
}
export default (compose(
connect(mapStateToProps, mapDispatchToProps),
withA,
withB,
)(Component): React.AbstractComponent<OwnProps>) // export the connected component without injected props
Benefits of this version
After fixing the implicit instantiation errors, if your code contains mismatched types between connected components, the total number of errors may go up. This is the result of Flow's improved coverage. If you are using console output for the Flow errors, you may not be able to see those errors until you clear other errors. These additional errors are grouped together, all tied back to React Redux's library definition, and have friendly error messages that will pin point you to the lines of code to the errors.
References
Articles
Upgrading guides
- Ville's and Jordan Brown's guide: Adding Type Parameters to Connect
- Quick Note Fixing
connect
FlowType Annotation after 0.89
Talks
- Flow Be Happy A talk on upgrading Flow past 0.85, slides
Others
- Flow Notes A repo where I herd my notes on Flow
- Flow Typed tests for React Redux
connect
- flow-typed/#2946: Discussion after 0.85
- Add support for Flow 0.89+: #3012, #3035
- What's
_
?
Top comments (0)