Skip to content

Commit 0ecf245

Browse files
committed
Updated docs and and example of integration with redux-thunk Resolved piotrwitek#119
1 parent 5d03b97 commit 0ecf245

10 files changed

+309
-188
lines changed

README.md

Lines changed: 103 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ _"This guide is a **living compendium** documenting the most important patterns
77
88
:tada: _Now updated to be compatible with **TypeScript v3.1.6**_ :tada:
99

10-
:computer: _Reference implementation of Todo-App with `typesafe-actions`: https://codesandbox.io/s/github/piotrwitek/typesafe-actions-todo-app_ :computer:
10+
:computer: _Reference implementation of Todo-App with `typesafe-actions`: https://codesandbox.io/s/github/piotrwitek/typesafe-actions/tree/master/codesandbox_ :computer:
1111

1212
### Goals
1313
- Complete type safety (with [`--strict`](https://www.typescriptlang.org/docs/handbook/compiler-options.html) flag) without losing type information downstream through all the layers of our application (e.g. no type assertions or hacking with `any` type)
@@ -59,9 +59,10 @@ Issues can be funded by anyone and the money will be transparently distributed t
5959
- [Testing reducer](#testing-reducer)
6060
- [Async Flow with `redux-observable`](#async-flow-with-redux-observable)
6161
- [Typing Epics](#typing-epics)
62-
- [Testing Epics](#testing-epics) 🌟 __NEW__
63-
- [Selectors](#selectors)
64-
- [Typing connect](#typing-connect)
62+
- [Testing Epics](#testing-epics)
63+
- [Async Flow with `redux-thunk`](#async-flow-with-redux-thunk) 🌟 __NEW__
64+
- [Selectors with `reselect`](#selectors-with-reselect)
65+
- [Connect with `react-redux`](#connect-with-react-redux) 🌟 __NEW__
6566
- [Tools](#tools)
6667
- [TSLint](#tslint)
6768
- [Jest](#jest)
@@ -138,7 +139,7 @@ const Component = ({ children: React.ReactNode }) => ...
138139
```
139140
140141
#### `React.CSSProperties`
141-
Type representing style object in JSX (usefull for css-in-js styles)
142+
Type representing style object in JSX (useful for css-in-js styles)
142143
```tsx
143144
const styles: React.CSSProperties = { flexDirection: 'row', ...
144145
const element = <div style={styles} ...
@@ -660,31 +661,32 @@ export default () => <FCCounterConnected label={'FCCounterConnected'} />;
660661
661662
[⇧ back to top](#table-of-contents)
662663
663-
#### - redux connected counter (verbose)
664+
#### - redux connected counter with own props
664665
665666
```tsx
666667
import Types from 'MyTypes';
667-
import { bindActionCreators, Dispatch } from 'redux';
668668
import { connect } from 'react-redux';
669669

670-
import { countersActions } from '../features/counters';
670+
import { countersActions, countersSelectors } from '../features/counters';
671671
import { FCCounter } from '../components';
672672

673-
const mapStateToProps = (state: Types.RootState) => ({
674-
count: state.counters.reduxCounter,
673+
type OwnProps = {
674+
initialCount?: number;
675+
};
676+
677+
const mapStateToProps = (state: Types.RootState, ownProps: OwnProps) => ({
678+
count:
679+
countersSelectors.getReduxCounter(state.counters) +
680+
(ownProps.initialCount || 0),
675681
});
676682

677-
const mapDispatchToProps = (dispatch: Dispatch<Types.RootAction>) =>
678-
bindActionCreators(
679-
{
680-
onIncrement: countersActions.increment,
681-
},
682-
dispatch
683-
);
683+
const dispatchProps = {
684+
onIncrement: countersActions.increment,
685+
};
684686

685-
export const FCCounterConnectedVerbose = connect(
687+
export const FCCounterConnectedOwnProps = connect(
686688
mapStateToProps,
687-
mapDispatchToProps
689+
dispatchProps
688690
)(FCCounter);
689691

690692
```
@@ -693,43 +695,77 @@ export const FCCounterConnectedVerbose = connect(
693695
```tsx
694696
import * as React from 'react';
695697

696-
import { FCCounterConnectedVerbose } from '.';
698+
import { FCCounterConnectedOwnProps } from '.';
697699

698700
export default () => (
699-
<FCCounterConnectedVerbose label={'FCCounterConnectedVerbose'} />
701+
<FCCounterConnectedOwnProps
702+
label={'FCCounterConnectedOwnProps'}
703+
initialCount={10}
704+
/>
700705
);
701706

702707
```
703708
</p></details>
704709
705710
[⇧ back to top](#table-of-contents)
706711
707-
#### - with own props
712+
#### - redux connected counter with `redux-thunk` integration
708713
709714
```tsx
710715
import Types from 'MyTypes';
716+
import { bindActionCreators, Dispatch } from 'redux';
711717
import { connect } from 'react-redux';
718+
import * as React from 'react';
712719

713-
import { countersActions, countersSelectors } from '../features/counters';
714-
import { FCCounter } from '../components';
720+
import { countersActions } from '../features/counters';
715721

716-
type OwnProps = {
717-
initialCount?: number;
722+
// Thunk Action
723+
const incrementWithDelay = () => async (dispatch: Dispatch): Promise<void> => {
724+
setTimeout(() => dispatch(countersActions.increment()), 1000);
718725
};
719726

720-
const mapStateToProps = (state: Types.RootState, ownProps: OwnProps) => ({
721-
count:
722-
countersSelectors.getReduxCounter(state.counters) +
723-
(ownProps.initialCount || 0),
727+
const mapStateToProps = (state: Types.RootState) => ({
728+
count: state.counters.reduxCounter,
724729
});
725730

726-
const dispatchProps = {
727-
onIncrement: countersActions.increment,
731+
const mapDispatchToProps = (dispatch: Dispatch<Types.RootAction>) =>
732+
bindActionCreators(
733+
{
734+
onIncrement: incrementWithDelay,
735+
},
736+
dispatch
737+
);
738+
739+
type Props = ReturnType<typeof mapStateToProps> &
740+
ReturnType<typeof mapDispatchToProps> & {
741+
label: string;
742+
};
743+
744+
export const FCCounter: React.FC<Props> = props => {
745+
const { label, count, onIncrement } = props;
746+
747+
const handleIncrement = () => {
748+
// Thunk action is correctly typed as promise
749+
onIncrement().then(() => {
750+
// ...
751+
});
752+
};
753+
754+
return (
755+
<div>
756+
<span>
757+
{label}: {count}
758+
</span>
759+
<button type="button" onClick={handleIncrement}>
760+
{`Increment`}
761+
</button>
762+
</div>
763+
);
728764
};
729765

730-
export const FCCounterConnectedExtended = connect(
766+
export const FCCounterConnectedBindActionCreators = connect(
731767
mapStateToProps,
732-
dispatchProps
768+
mapDispatchToProps
733769
)(FCCounter);
734770

735771
```
@@ -738,12 +774,11 @@ export const FCCounterConnectedExtended = connect(
738774
```tsx
739775
import * as React from 'react';
740776

741-
import { FCCounterConnectedExtended } from '.';
777+
import { FCCounterConnectedBindActionCreators } from '.';
742778

743779
export default () => (
744-
<FCCounterConnectedExtended
745-
label={'FCCounterConnectedExtended'}
746-
initialCount={10}
780+
<FCCounterConnectedBindActionCreators
781+
label={'FCCounterConnectedBindActionCreators'}
747782
/>
748783
);
749784

@@ -1043,9 +1078,9 @@ export default store;
10431078
## Action Creators
10441079
10451080
> We'll be using a battle-tested library [![NPM Downloads](https://img.shields.io/npm/dm/typesafe-actions.svg)](https://www.npmjs.com/package/typesafe-actions)
1046-
that automates and simplify maintenace of **type annotations in Redux Architectures** [`typesafe-actions`](https://github.com/piotrwitek/typesafe-actions#typesafe-actions)
1081+
that'll help retain complete type soundness and simplify maintenace of **types in Redux Architectures** [`typesafe-actions`](https://github.com/piotrwitek/typesafe-actions#typesafe-actions)
10471082
1048-
### For more examples and in-depth tutorial you should check [The Mighty Tutorial](https://github.com/piotrwitek/typesafe-actions#behold-the-mighty-tutorial)!
1083+
> You can find more real-world examples and in-depth tutorial in: [Typesafe-Actions - The Mighty Tutorial](https://github.com/piotrwitek/typesafe-actions#behold-the-mighty-tutorial)!
10491084
10501085
A solution below is using a simple factory function to automate the creation of type-safe action creators. The goal is to decrease maintenance effort and reduce code repetition of type annotations for actions and creators. The result is completely typesafe action-creators and their actions.
10511086
@@ -1267,8 +1302,6 @@ describe('Todos Stories', () => {
12671302
12681303
## Async Flow with `redux-observable`
12691304
1270-
### For more examples and in-depth tutorial you should check [The Mighty Tutorial](https://github.com/piotrwitek/typesafe-actions#behold-the-mighty-tutorial)!
1271-
12721305
### Typing epics
12731306
12741307
```tsx
@@ -1361,9 +1394,7 @@ describe('Todos Epics', () => {
13611394
13621395
---
13631396
1364-
## Selectors
1365-
1366-
### "reselect"
1397+
## Selectors with `reselect`
13671398
13681399
```tsx
13691400
import { createSelector } from 'reselect';
@@ -1392,9 +1423,9 @@ export const getFilteredTodos = createSelector(getTodos, getTodosFilter, (todos,
13921423
13931424
---
13941425
1395-
## Typing connect
1426+
## Connect with `react-redux`
13961427
1397-
*__NOTE__: Below you'll find only a short explanation of concepts behind typing `connect`. For more advanced scenarios and common use-cases (`redux-thunk` and more...) please check [Redux Connected Components](#redux-connected-components) section.*
1428+
*__NOTE__: Below you'll find only a short explanation of concepts behind typing `connect`. For more real-world examples please check [Redux Connected Components](#redux-connected-components) section.*
13981429
13991430
```tsx
14001431
import MyTypes from 'MyTypes';
@@ -1425,10 +1456,34 @@ const dispatchToProps = {
14251456
// Notice ee don't need to pass any generic type parameters to neither connect nor map functions above
14261457
// because type inference will infer types from arguments annotations automatically
14271458
// It's much cleaner and idiomatic approach
1428-
export const FCCounterConnectedVerbose =
1459+
export const FCCounterConnected =
14291460
connect(mapStateToProps, mapDispatchToProps)(FCCounter);
14301461
```
14311462
1463+
*__NOTE__ (for `redux-thunk`): When using thunk action creators you need to use `bindActionCreators`. Only this way you can get corrected dispatch props type signature like below.*
1464+
1465+
*__WARNING__: As of now (Apr 2019) `bindActionCreators` signature of the latest `redux-thunk` release will not work as below, you need to use updated type definitions that you can find in `/playground/typings/redux-thunk` folder and then add paths overload in your tsconfig like this: `"paths":{"redux-thunk":["typings/redux-thunk"]}`.*
1466+
1467+
```tsx
1468+
const thunkAsyncAction = () => async (dispatch: Dispatch): Promise<void> => {
1469+
// dispatch actions, return Promise, etc.
1470+
}
1471+
1472+
const mapDispatchToProps = (dispatch: Dispatch<Types.RootAction>) =>
1473+
bindActionCreators(
1474+
{
1475+
thunkAsyncAction,
1476+
},
1477+
dispatch
1478+
);
1479+
1480+
type DispatchProps = ReturnType<typeof mapDispatchToProps>;
1481+
// { thunkAsyncAction: () => Promise<void>; }
1482+
1483+
/* Without "bindActionCreators" fix signature will be the same as the original "unbound" thunk function: */
1484+
// { thunkAsyncAction: () => (dispatch: Dispatch<AnyAction>) => Promise<void>; }
1485+
```
1486+
14321487
[⇧ back to top](#table-of-contents)
14331488
14341489
---

0 commit comments

Comments
 (0)