Skip to content

Commit a6aa9d2

Browse files
authored
Add utility types and refactor (piotrwitek#47)
* refactored to use utility-types library * remove duplicated globals * refactored redux modules
1 parent 4f2ef53 commit a6aa9d2

21 files changed

+128
-142
lines changed

README.md

+70-68
Original file line numberDiff line numberDiff line change
@@ -251,44 +251,43 @@ interface State {
251251
count: number;
252252
}
253253

254-
export class StatefulCounterWithDefault extends React.Component<StatefulCounterWithDefaultProps, State> {
255-
// to make defaultProps strictly typed we need to explicitly declare their type
256-
// @see https://github.com/DefinitelyTyped/DefinitelyTyped/issues/11640
257-
static defaultProps: DefaultProps = {
258-
initialCount: 0,
259-
};
260-
261-
props: StatefulCounterWithDefaultProps & DefaultProps;
254+
export const StatefulCounterWithDefault: React.ComponentClass<StatefulCounterWithDefaultProps> =
255+
class extends React.Component<StatefulCounterWithDefaultProps & DefaultProps> {
256+
// to make defaultProps strictly typed we need to explicitly declare their type
257+
// @see https://github.com/DefinitelyTyped/DefinitelyTyped/issues/11640
258+
static defaultProps: DefaultProps = {
259+
initialCount: 0,
260+
};
262261

263-
state: State = {
264-
count: this.props.initialCount,
265-
};
262+
state: State = {
263+
count: this.props.initialCount,
264+
};
266265

267-
componentWillReceiveProps({ initialCount }: StatefulCounterWithDefaultProps) {
268-
if (initialCount != null && initialCount !== this.props.initialCount) {
269-
this.setState({ count: initialCount });
266+
componentWillReceiveProps({ initialCount }: StatefulCounterWithDefaultProps) {
267+
if (initialCount != null && initialCount !== this.props.initialCount) {
268+
this.setState({ count: initialCount });
269+
}
270270
}
271-
}
272271

273-
handleIncrement = () => {
274-
this.setState({ count: this.state.count + 1 });
275-
}
272+
handleIncrement = () => {
273+
this.setState({ count: this.state.count + 1 });
274+
}
276275

277-
render() {
278-
const { handleIncrement } = this;
279-
const { label } = this.props;
280-
const { count } = this.state;
276+
render() {
277+
const { handleIncrement } = this;
278+
const { label } = this.props;
279+
const { count } = this.state;
281280

282-
return (
283-
<div>
284-
<span>{label}: {count} </span>
285-
<button type="button" onClick={handleIncrement}>
286-
{`Increment`}
287-
</button>
288-
</div>
289-
);
290-
}
291-
}
281+
return (
282+
<div>
283+
<span>{label}: {count} </span>
284+
<button type="button" onClick={handleIncrement}>
285+
{`Increment`}
286+
</button>
287+
</div>
288+
);
289+
}
290+
};
292291

293292
```
294293
@@ -342,7 +341,7 @@ Adds state to a stateless counter
342341
343342
```tsx
344343
import * as React from 'react';
345-
import { Diff as Subtract } from 'react-redux-typescript';
344+
import { Subtract } from 'utility-types';
346345

347346
// These props will be subtracted from original component type
348347
interface WrappedComponentProps {
@@ -414,7 +413,7 @@ Adds error handling using componentDidCatch to any component
414413
415414
```tsx
416415
import * as React from 'react';
417-
import { Diff as Subtract } from 'react-redux-typescript';
416+
import { Subtract } from 'utility-types';
418417

419418
const MISSING_ERROR = 'Error was swallowed during propagation.';
420419

@@ -521,15 +520,15 @@ const mapDispatchToProps = (dispatch: Dispatch) => ({
521520
import { connect } from 'react-redux';
522521

523522
import { RootState } from '@src/redux';
524-
import { actions, CountersSelectors } from '@src/redux/counters';
523+
import { countersActions, CountersSelectors } from '@src/redux/counters';
525524
import { SFCCounter } from '@src/components';
526525

527526
const mapStateToProps = (state: RootState) => ({
528527
count: CountersSelectors.getReduxCounter(state),
529528
});
530529

531530
export const SFCCounterConnected = connect(mapStateToProps, {
532-
onIncrement: actions.increment,
531+
onIncrement: countersActions.increment,
533532
})(SFCCounter);
534533

535534
```
@@ -558,15 +557,15 @@ import { bindActionCreators } from 'redux';
558557
import { connect } from 'react-redux';
559558

560559
import { RootState, Dispatch } from '@src/redux';
561-
import { actions } from '@src/redux/counters';
560+
import { countersActions } from '@src/redux/counters';
562561
import { SFCCounter } from '@src/components';
563562

564563
const mapStateToProps = (state: RootState) => ({
565564
count: state.counters.reduxCounter,
566565
});
567566

568567
const mapDispatchToProps = (dispatch: Dispatch) => bindActionCreators({
569-
onIncrement: actions.increment,
568+
onIncrement: countersActions.increment,
570569
}, dispatch);
571570

572571
export const SFCCounterConnectedVerbose =
@@ -597,7 +596,7 @@ export default () => (
597596
import { connect } from 'react-redux';
598597

599598
import { RootState } from '@src/redux';
600-
import { actions, CountersSelectors } from '@src/redux/counters';
599+
import { countersActions, CountersSelectors } from '@src/redux/counters';
601600
import { SFCCounter } from '@src/components';
602601

603602
export interface SFCCounterConnectedExtended {
@@ -609,7 +608,7 @@ const mapStateToProps = (state: RootState, ownProps: SFCCounterConnectedExtended
609608
});
610609

611610
export const SFCCounterConnectedExtended = connect(mapStateToProps, {
612-
onIncrement: actions.increment,
611+
onIncrement: countersActions.increment,
613612
})(SFCCounter);
614613

615614
```
@@ -647,7 +646,7 @@ All that without losing type-safety! Please check this very short [Tutorial](htt
647646
```tsx
648647
import { createAction } from 'typesafe-actions';
649648

650-
export const actions = {
649+
export const countersActions = {
651650
increment: createAction('INCREMENT'),
652651
add: createAction('ADD', (amount: number) => ({
653652
type: 'ADD',
@@ -660,10 +659,10 @@ export const actions = {
660659
661660
```tsx
662661
import store from '@src/store';
663-
import { actions } from '@src/redux/counters';
662+
import { countersActions } from '@src/redux/counters';
664663

665664
// store.dispatch(actionCreators.increment(1)); // Error: Expected 0 arguments, but got 1.
666-
store.dispatch(actions.increment()); // OK => { type: "INCREMENT" }
665+
store.dispatch(countersActions.increment()); // OK => { type: "INCREMENT" }
667666

668667
```
669668
</p></details>
@@ -738,7 +737,7 @@ import { getType } from 'typesafe-actions';
738737

739738
import { RootAction } from '@src/redux';
740739

741-
import { actions } from './';
740+
import { countersActions } from './';
742741

743742
export type State = {
744743
readonly reduxCounter: number;
@@ -747,11 +746,11 @@ export type State = {
747746
export const reducer = combineReducers<State, RootAction>({
748747
reduxCounter: (state = 0, action) => {
749748
switch (action.type) {
750-
case getType(actions.increment):
751-
return state + 1;
749+
case getType(countersActions.increment):
750+
return state + 1; // action is type: { type: "INCREMENT"; }
752751

753-
case getType(actions.add):
754-
return state + action.payload;
752+
case getType(countersActions.add):
753+
return state + action.payload; // action is type: { type: "ADD"; payload: number; }
755754

756755
default:
757756
return state;
@@ -804,21 +803,19 @@ Can be imported in various layers receiving or sending redux actions like: reduc
804803
```tsx
805804
// RootActions
806805
import { RouterAction, LocationChangeAction } from 'react-router-redux';
807-
import { getReturnOfExpression } from 'react-redux-typescript';
806+
import { $call } from 'utility-types';
808807

809-
import { actions as countersAC } from '@src/redux/counters';
810-
import { actions as todosAC } from '@src/redux/todos';
811-
import { actions as toastsAC } from '@src/redux/toasts';
808+
import { countersActions } from '@src/redux/counters';
809+
import { todosActions } from '@src/redux/todos';
810+
import { toastsActions } from '@src/redux/toasts';
812811

813-
export const allActions = {
814-
...countersAC,
815-
...todosAC,
816-
...toastsAC,
817-
};
812+
const returnsOfActions = [
813+
...Object.values(countersActions),
814+
...Object.values(todosActions),
815+
...Object.values(toastsActions),
816+
].map($call);
818817

819-
const returnOfActions =
820-
Object.values(allActions).map(getReturnOfExpression);
821-
type AppAction = typeof returnOfActions[number];
818+
type AppAction = typeof returnsOfActions[number];
822819
type ReactRouterAction = RouterAction | LocationChangeAction;
823820

824821
export type RootAction =
@@ -875,25 +872,28 @@ export default store;
875872
876873
### "redux-observable"
877874
875+
Use `isActionOf` helper to filter actions and to narrow `RootAction` union type to a specific "action type" down the stream.
876+
878877
```tsx
879878
import { combineEpics, Epic } from 'redux-observable';
880879
import { isActionOf } from 'typesafe-actions';
881880
import { Observable } from 'rxjs/Observable';
882881
import { v4 } from 'uuid';
883882

884-
import { RootAction, RootState, allActions } from '@src/redux';
885-
import { actions } from './';
883+
import { RootAction, RootState } from '@src/redux';
884+
import { todosActions } from '@src/redux/todos';
885+
import { toastsActions } from './';
886886

887887
const TOAST_LIFETIME = 2000;
888888

889889
const addTodoToast: Epic<RootAction, RootState> =
890890
(action$, store) => action$
891-
.filter(isActionOf(allActions.addTodo))
892-
.concatMap((action) => {
891+
.filter(isActionOf(todosActions.addTodo))
892+
.concatMap((action) => { // action is type: { type: "ADD_TODO"; payload: string; }
893893
const toast = { id: v4(), text: action.payload };
894894

895-
const addToast$ = Observable.of(actions.addToast(toast));
896-
const removeToast$ = Observable.of(actions.removeToast(toast.id))
895+
const addToast$ = Observable.of(toastsActions.addToast(toast));
896+
const removeToast$ = Observable.of(toastsActions.removeToast(toast.id))
897897
.delay(TOAST_LIFETIME);
898898

899899
return addToast$.concat(removeToast$);
@@ -965,7 +965,9 @@ export type Actions = {
965965
type: typeof ADD,
966966
payload: number,
967967
},
968-
};
968+
};
969+
970+
export type RootAction = Actions[keyof Actions];
969971

970972
export const actions = {
971973
increment: (): Actions[typeof INCREMENT] => ({
@@ -979,6 +981,7 @@ export const actions = {
979981
```
980982
981983
[⇧ back to top](#table-of-contents)
984+
982985
---
983986
984987
# Tools
@@ -1307,7 +1310,6 @@ node ./generator/bin/generate-readme.js
13071310
13081311
# Project Examples
13091312
1310-
https://github.com/piotrwitek/react-redux-typescript-starter-kit
13111313
https://github.com/piotrwitek/react-redux-typescript-webpack-starter
13121314
13131315
[⇧ back to top](#table-of-contents)

docs/markdown/2_redux.md

+6-2
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,8 @@ When creating the store, use rootReducer. This will set-up a **strongly typed St
112112

113113
### "redux-observable"
114114

115+
Use `isActionOf` helper to filter actions and to narrow `RootAction` union type to a specific "action type" down the stream.
116+
115117
::example='../../playground/src/redux/toasts/epics.ts'::
116118

117119
[⇧ back to top](#table-of-contents)
@@ -176,7 +178,9 @@ export type Actions = {
176178
type: typeof ADD,
177179
payload: number,
178180
},
179-
};
181+
};
182+
183+
export type RootAction = Actions[keyof Actions];
180184

181185
export const actions = {
182186
increment: (): Actions[typeof INCREMENT] => ({
@@ -189,4 +193,4 @@ export const actions = {
189193
};
190194
```
191195

192-
[⇧ back to top](#table-of-contents)
196+
[⇧ back to top](#table-of-contents)

docs/markdown/6_end.md

-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ node ./generator/bin/generate-readme.js
2727

2828
# Project Examples
2929

30-
https://github.com/piotrwitek/react-redux-typescript-starter-kit
3130
https://github.com/piotrwitek/react-redux-typescript-webpack-starter
3231

3332
[⇧ back to top](#table-of-contents)

playground/package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -25,15 +25,15 @@
2525
"react": "16.2.0",
2626
"react-dom": "16.2.0",
2727
"react-redux": "5.0.6",
28-
"react-redux-typescript": "3.0.0-rc.3",
2928
"react-router-dom": "4.2.2",
3029
"react-router-redux": "5.0.0-alpha.8",
3130
"redux": "3.7.2",
3231
"redux-observable": "0.17.0",
3332
"reselect": "3.0.1",
3433
"rxjs": "5.5.6",
3534
"tslib": "1.8.1",
36-
"typesafe-actions": "1.1.0",
35+
"typesafe-actions": "1.1.2",
36+
"utility-types": "1.0.0",
3737
"uuid": "3.1.0"
3838
},
3939
"devDependencies": {

playground/src/connected/sfc-counter-connected-extended.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { connect } from 'react-redux';
22

33
import { RootState } from '@src/redux';
4-
import { actions, CountersSelectors } from '@src/redux/counters';
4+
import { countersActions, CountersSelectors } from '@src/redux/counters';
55
import { SFCCounter } from '@src/components';
66

77
export interface SFCCounterConnectedExtended {
@@ -13,5 +13,5 @@ const mapStateToProps = (state: RootState, ownProps: SFCCounterConnectedExtended
1313
});
1414

1515
export const SFCCounterConnectedExtended = connect(mapStateToProps, {
16-
onIncrement: actions.increment,
16+
onIncrement: countersActions.increment,
1717
})(SFCCounter);

playground/src/connected/sfc-counter-connected-verbose.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,15 @@ import { bindActionCreators } from 'redux';
22
import { connect } from 'react-redux';
33

44
import { RootState, Dispatch } from '@src/redux';
5-
import { actions } from '@src/redux/counters';
5+
import { countersActions } from '@src/redux/counters';
66
import { SFCCounter } from '@src/components';
77

88
const mapStateToProps = (state: RootState) => ({
99
count: state.counters.reduxCounter,
1010
});
1111

1212
const mapDispatchToProps = (dispatch: Dispatch) => bindActionCreators({
13-
onIncrement: actions.increment,
13+
onIncrement: countersActions.increment,
1414
}, dispatch);
1515

1616
export const SFCCounterConnectedVerbose =
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
import { connect } from 'react-redux';
22

33
import { RootState } from '@src/redux';
4-
import { actions, CountersSelectors } from '@src/redux/counters';
4+
import { countersActions, CountersSelectors } from '@src/redux/counters';
55
import { SFCCounter } from '@src/components';
66

77
const mapStateToProps = (state: RootState) => ({
88
count: CountersSelectors.getReduxCounter(state),
99
});
1010

1111
export const SFCCounterConnected = connect(mapStateToProps, {
12-
onIncrement: actions.increment,
12+
onIncrement: countersActions.increment,
1313
})(SFCCounter);

0 commit comments

Comments
 (0)