diff --git a/docs/using-react-redux/static-typing-with-flow.md b/docs/using-react-redux/static-typing-with-flow.md new file mode 100644 index 000000000..7733ec69e --- /dev/null +++ b/docs/using-react-redux/static-typing-with-flow.md @@ -0,0 +1,252 @@ +--- +id: static-typing-with-flow +title: Static Typing with Flow +hide_title: true +sidebar_label: Flow +--- + +# Static Typing with Flow + +After Flow 0.85, Flow starts [Asking for Required Annotations](https://medium.com/flow-type/asking-for-required-annotations-64d4f9c1edf8) 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 to `mapStateToProps`. If there are props that are not used by `mapStateToProps`, i.e., the props that "pass through", include them here in `OwnProps` as well +- `Props`: `OwnProps` plus the props passed in by `mapStateToProps` and `mapDispatchToProps` + +> **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. + +```js +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`: + +```jsx +import * as React from 'react' + +const MyComponent = (props: Props) => ( +
+ {props.passthrough} + {props.fromMapStateToProps} +
+) +``` + +When we export, this is also when we normally call `connect`, annotate the exported component with _just_ `OwnProps`: + +```jsx +import * as React from 'react' + +// const MyComponent = ... + +export default (connect( + mapStateToProps, + mapDispatchToProps +)(MyComponent): React.AbstractComponent) +``` + +### 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](https://github.com/flow-typed/flow-typed/blob/master/definitions/npm/react-redux_v5.x.x/flow_v0.89.x-/react-redux_v5.x.x.js). Note that this will also require Flow v0.89+. + +The Flow Typed library definition declares `connect` as follows: + +```js +declare export function connect<-P, -OP, -SP, -DP, -S, -D>( + mapStateToProps?: null | void, + mapDispatchToProps?: null | void, + mergeProps?: null | void, + options?: ?Options> +): Connector> +``` + +The libdef also contains a [glossary of the abbreviations](https://github.com/flow-typed/flow-typed/blob/master/definitions/npm/react-redux_v5.x.x/flow_v0.89.x-/react-redux_v5.x.x.js#L14) which decrypts the signature to: + +```jsx +connect(…) +``` + +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?](https://github.com/facebook/flow/commit/ec70da4510d3a092fa933081c083bd0e513d0518)) as placeholder at unused type parameter positions. A common `connect` call may look like this: + +```jsx +connect(…) +``` + +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` and `mapDispatchToProps` of action creators + +#### Connecting stateless component with `mapStateToProps` + +```jsx +type OwnProps = {| + passthrough: number, + forMapStateToProps: string, +|}; +type Props = {| + ...OwnProps, + fromStateToProps: string +|}; +const Com = (props: Props) =>
{props.passthrough} {props.fromStateToProps}
+ +type State = {a: number}; +const mapStateToProps = (state: State, props: OwnProps) => { + return { + fromStateToProps: 'str' + state.a + } +}; + +const Connected = connect(mapStateToProps)(Com); +``` + +#### Connecting components with `mapDispatchToProps` of action creators + +```jsx +type OwnProps = {| + passthrough: number, +|}; +type Props = {| + ...OwnProps, + dispatch1: (num: number) => void, + dispatch2: () => void +|}; +class Com extends React.Component { + render() { + return
{this.props.passthrough}
; + } +} + +const mapDispatchToProps = { + dispatch1: (num: number) => {}, + dispatch2: () => {} +}; +const Connected = connect(null, mapDispatchToProps)(Com); +e.push(Connected); +; +``` + +#### Connecting components with `mapStateToProps` and `mapDispatchToProps` of action creators + +```jsx +type OwnProps = {| + passthrough: number, + forMapStateToProps: string +|}; +type Props = {| + ...OwnProps, + dispatch1: () => void, + dispatch2: () => void, + fromMapStateToProps: number +|}; +class Com extends React.Component { + render() { + return
{this.props.passthrough}
; + } +} +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(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: + +```jsx +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) // 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. + +![](https://i.imgur.com/mt79yaC.png) + +## References + +**Articles** + +- [Asking for Required Annotations](https://medium.com/flow-type/asking-for-required-annotations-64d4f9c1edf8) + +**Upgrading guides** + +- [Ville's and Jordan Brown's guide: _Adding Type Parameters to Connect_](https://gist.github.com/jbrown215/f425203ef30fdc8a28c213b90ba7a794) +- [Quick Note Fixing `connect` FlowType Annotation after 0.89](https://dev.to/wgao19/quick-note-fixing-connect-flowtype-annotation-after-089-joi) + +**Talks** + +- [Flow Be Happy](https://engineers.sg/video/flow-be-happy-reactjs-singapore--3419) A talk on upgrading Flow past 0.85, [slides](https://flow-be-happy.netlify.com/) + +**Others** + +- [Flow Typed tests for React Redux `connect`](https://github.com/flow-typed/flow-typed/blob/master/definitions/npm/react-redux_v5.x.x/flow_v0.89.x-/test_connect.js) +- [flow-typed/#2946: Discussion after 0.85](https://github.com/flow-typed/flow-typed/issues/2946) +- Add support for Flow 0.89+: [#3012](https://github.com/flow-typed/flow-typed/pull/3035), [#3035](https://github.com/flow-typed/flow-typed/pull/3035) +- [What's `_`?](https://github.com/facebook/flow/commit/ec70da4510d3a092fa933081c083bd0e513d0518) diff --git a/docs/using-react-redux/static-typing-with-typescript.md b/docs/using-react-redux/static-typing-with-typescript.md new file mode 100644 index 000000000..d0bb63e70 --- /dev/null +++ b/docs/using-react-redux/static-typing-with-typescript.md @@ -0,0 +1,10 @@ +--- +id: static-typing-with-typescript +title: Static Typing with TypeScript +hide_title: true +sidebar_label: TypeScript +--- + +# Static Typing with TypeScript + +We're looking for help on this section, [file a PR](https://github.com/reduxjs/react-redux/issues/1001#issuecomment-503532530). diff --git a/website/sidebars.json b/website/sidebars.json index d2947ec6d..44515e867 100644 --- a/website/sidebars.json +++ b/website/sidebars.json @@ -8,7 +8,15 @@ "Using React Redux": [ "using-react-redux/connect-mapstate", "using-react-redux/connect-mapdispatch", - "using-react-redux/accessing-store" + "using-react-redux/accessing-store", + { + "type": "subcategory", + "label": "Static Typing", + "ids": [ + "using-react-redux/static-typing-with-flow", + "using-react-redux/static-typing-with-typescript" + ] + } ], "API Reference": [ "api/connect", diff --git a/website/versioned_docs/version-5.x/using-react-redux/static-typing-with-flow.md b/website/versioned_docs/version-5.x/using-react-redux/static-typing-with-flow.md new file mode 100644 index 000000000..0a42c24d1 --- /dev/null +++ b/website/versioned_docs/version-5.x/using-react-redux/static-typing-with-flow.md @@ -0,0 +1,253 @@ +--- +id: version-5.x-static-typing-with-flow +title: Static Typing with Flow +hide_title: true +sidebar_label: Flow +original_id: static-typing-with-flow +--- + +# Static Typing with Flow + +After Flow 0.85, Flow starts [Asking for Required Annotations](https://medium.com/flow-type/asking-for-required-annotations-64d4f9c1edf8) 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 to `mapStateToProps`. If there are props that are not used by `mapStateToProps`, i.e., the props that "pass through", include them here in `OwnProps` as well +- `Props`: `OwnProps` plus the props passed in by `mapStateToProps` and `mapDispatchToProps` + +> **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. + +```js +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`: + +```jsx +import * as React from 'react' + +const MyComponent = (props: Props) => ( +
+ {props.passthrough} + {props.fromMapStateToProps} +
+) +``` + +When we export, this is also when we normally call `connect`, annotate the exported component with _just_ `OwnProps`: + +```jsx +import * as React from 'react' + +// const MyComponent = ... + +export default (connect( + mapStateToProps, + mapDispatchToProps +)(MyComponent): React.AbstractComponent) +``` + +### 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](https://github.com/flow-typed/flow-typed/blob/master/definitions/npm/react-redux_v5.x.x/flow_v0.89.x-/react-redux_v5.x.x.js). Note that this will also require Flow v0.89+. + +The Flow Typed library definition declares `connect` as follows: + +```js +declare export function connect<-P, -OP, -SP, -DP, -S, -D>( + mapStateToProps?: null | void, + mapDispatchToProps?: null | void, + mergeProps?: null | void, + options?: ?Options> +): Connector> +``` + +The libdef also contains a [glossary of the abbreviations](https://github.com/flow-typed/flow-typed/blob/master/definitions/npm/react-redux_v5.x.x/flow_v0.89.x-/react-redux_v5.x.x.js#L14) which decrypts the signature to: + +```jsx +connect(…) +``` + +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?](https://github.com/facebook/flow/commit/ec70da4510d3a092fa933081c083bd0e513d0518)) as placeholder at unused type parameter positions. A common `connect` call may look like this: + +```jsx +connect(…) +``` + +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` and `mapDispatchToProps` of action creators + +#### Connecting stateless component with `mapStateToProps` + +```jsx +type OwnProps = {| + passthrough: number, + forMapStateToProps: string, +|}; +type Props = {| + ...OwnProps, + fromStateToProps: string +|}; +const Com = (props: Props) =>
{props.passthrough} {props.fromStateToProps}
+ +type State = {a: number}; +const mapStateToProps = (state: State, props: OwnProps) => { + return { + fromStateToProps: 'str' + state.a + } +}; + +const Connected = connect(mapStateToProps)(Com); +``` + +#### Connecting components with `mapDispatchToProps` of action creators + +```jsx +type OwnProps = {| + passthrough: number, +|}; +type Props = {| + ...OwnProps, + dispatch1: (num: number) => void, + dispatch2: () => void +|}; +class Com extends React.Component { + render() { + return
{this.props.passthrough}
; + } +} + +const mapDispatchToProps = { + dispatch1: (num: number) => {}, + dispatch2: () => {} +}; +const Connected = connect(null, mapDispatchToProps)(Com); +e.push(Connected); +; +``` + +#### Connecting components with `mapStateToProps` and `mapDispatchToProps` of action creators + +```jsx +type OwnProps = {| + passthrough: number, + forMapStateToProps: string +|}; +type Props = {| + ...OwnProps, + dispatch1: () => void, + dispatch2: () => void, + fromMapStateToProps: number +|}; +class Com extends React.Component { + render() { + return
{this.props.passthrough}
; + } +} +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(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: + +```jsx +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) // 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. + +![](https://i.imgur.com/mt79yaC.png) + +## References + +**Articles** + +- [Asking for Required Annotations](https://medium.com/flow-type/asking-for-required-annotations-64d4f9c1edf8) + +**Upgrading guides** + +- [Ville's and Jordan Brown's guide: _Adding Type Parameters to Connect_](https://gist.github.com/jbrown215/f425203ef30fdc8a28c213b90ba7a794) +- [Quick Note Fixing `connect` FlowType Annotation after 0.89](https://dev.to/wgao19/quick-note-fixing-connect-flowtype-annotation-after-089-joi) + +**Talks** + +- [Flow Be Happy](https://engineers.sg/video/flow-be-happy-reactjs-singapore--3419) A talk on upgrading Flow past 0.85, [slides](https://flow-be-happy.netlify.com/) + +**Others** + +- [Flow Typed tests for React Redux `connect`](https://github.com/flow-typed/flow-typed/blob/master/definitions/npm/react-redux_v5.x.x/flow_v0.89.x-/test_connect.js) +- [flow-typed/#2946: Discussion after 0.85](https://github.com/flow-typed/flow-typed/issues/2946) +- Add support for Flow 0.89+: [#3012](https://github.com/flow-typed/flow-typed/pull/3035), [#3035](https://github.com/flow-typed/flow-typed/pull/3035) +- [What's `_`?](https://github.com/facebook/flow/commit/ec70da4510d3a092fa933081c083bd0e513d0518) diff --git a/website/versioned_docs/version-5.x/using-react-redux/static-typing-with-typescript.md b/website/versioned_docs/version-5.x/using-react-redux/static-typing-with-typescript.md new file mode 100644 index 000000000..e260e739b --- /dev/null +++ b/website/versioned_docs/version-5.x/using-react-redux/static-typing-with-typescript.md @@ -0,0 +1,11 @@ +--- +id: version-5.x-static-typing-with-typescript +title: Static Typing with TypeScript +hide_title: true +sidebar_label: TypeScript +original_id: static-typing-with-typescript +--- + +# Static Typing with TypeScript + +We're looking for help on this section, [file a PR](https://github.com/reduxjs/react-redux/issues/1001#issuecomment-503532530). diff --git a/website/versioned_sidebars/version-5.x-sidebars.json b/website/versioned_sidebars/version-5.x-sidebars.json index 07aabf72e..614969ec4 100644 --- a/website/versioned_sidebars/version-5.x-sidebars.json +++ b/website/versioned_sidebars/version-5.x-sidebars.json @@ -7,7 +7,15 @@ ], "Using React-Redux": [ "version-5.x-using-react-redux/connect-mapstate", - "version-5.x-using-react-redux/connect-mapdispatch" + "version-5.x-using-react-redux/connect-mapdispatch", + { + "type": "subcategory", + "label": "Static Typing", + "ids": [ + "version-5.x-using-react-redux/static-typing-with-flow", + "version-5.x-using-react-redux/static-typing-with-typescript" + ] + } ], "API Reference": [ "version-5.x-api", @@ -15,8 +23,6 @@ "version-5.x-api/connect-advanced", "version-5.x-api/provider" ], - "Guides": [ - "version-5.x-troubleshooting" - ] + "Guides": ["version-5.x-troubleshooting"] } } diff --git a/website/versioned_sidebars/version-6.x-sidebars.json b/website/versioned_sidebars/version-6.x-sidebars.json index 3e43837af..e72618fbe 100644 --- a/website/versioned_sidebars/version-6.x-sidebars.json +++ b/website/versioned_sidebars/version-6.x-sidebars.json @@ -8,15 +8,21 @@ "Using React Redux": [ "version-6.x-using-react-redux/connect-mapstate", "version-6.x-using-react-redux/connect-mapdispatch", - "version-6.x-using-react-redux/accessing-store" + "version-6.x-using-react-redux/accessing-store", + { + "type": "subcategory", + "label": "Static Typing", + "ids": [ + "version-6.x-using-react-redux/static-typing-with-flow", + "version-6.x-using-react-redux/static-typing-with-typescript" + ] + } ], "API Reference": [ "version-6.x-api/connect", "version-6.x-api/provider", "version-6.x-api/connect-advanced" ], - "Guides": [ - "version-6.x-troubleshooting" - ] + "Guides": ["version-6.x-troubleshooting"] } } diff --git a/website/versioned_sidebars/version-7.0-sidebars.json b/website/versioned_sidebars/version-7.0-sidebars.json index dc9450640..84878ada7 100644 --- a/website/versioned_sidebars/version-7.0-sidebars.json +++ b/website/versioned_sidebars/version-7.0-sidebars.json @@ -8,7 +8,15 @@ "Using React Redux": [ "version-7.0-using-react-redux/connect-mapstate", "version-7.0-using-react-redux/connect-mapdispatch", - "version-7.0-using-react-redux/accessing-store" + "version-7.0-using-react-redux/accessing-store", + { + "type": "subcategory", + "label": "Static Typing", + "ids": [ + "version-7.0-using-react-redux/static-typing-with-flow", + "version-7.0-using-react-redux/static-typing-with-typescript" + ] + } ], "API Reference": [ "version-7.0-api/connect", diff --git a/website/versioned_sidebars/version-7.1-sidebars.json b/website/versioned_sidebars/version-7.1-sidebars.json index e184f7756..f41272fc1 100644 --- a/website/versioned_sidebars/version-7.1-sidebars.json +++ b/website/versioned_sidebars/version-7.1-sidebars.json @@ -8,7 +8,15 @@ "Using React Redux": [ "version-7.1-using-react-redux/connect-mapstate", "version-7.1-using-react-redux/connect-mapdispatch", - "version-7.1-using-react-redux/accessing-store" + "version-7.1-using-react-redux/accessing-store", + { + "type": "subcategory", + "label": "Static Typing", + "ids": [ + "version-7.1-using-react-redux/static-typing-with-flow", + "version-7.1-using-react-redux/static-typing-with-typescript" + ] + } ], "API Reference": [ "version-7.1-api/connect", @@ -17,8 +25,6 @@ "version-7.1-api/batch", "version-7.1-api/hooks" ], - "Guides": [ - "version-7.1-troubleshooting" - ] + "Guides": ["version-7.1-troubleshooting"] } }