Skip to content

Commit 22128b8

Browse files
authored
Update README.md
1 parent 265af4b commit 22128b8

File tree

1 file changed

+99
-28
lines changed

1 file changed

+99
-28
lines changed

README.md

Lines changed: 99 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -309,14 +309,14 @@ export default connect(mapStateToProps, dispatchToProps)(CurrencyConverterContai
309309

310310
## Actions
311311

312-
### KISS Approach
313-
- more boilerplate
314-
- classic const based types
315-
- close to standard JS usage
316-
- need to export both const type and action creator to use in multiple reducer files or redux-observable modules
317-
- using `returntypeof()` helper function to infer Action types - (https://github.com/piotrwitek/react-redux-typescript#returntypeof-polyfill)
312+
### KISS Solution
313+
This solution is focused on KISS principle, without introducing any abstractions to be as close as possible to common Redux Pattern used in regular JavaScript solutions:
318314

319-
This case is focused on KISS, without introducing any abstractions to be as close as possible to common Redux Pattern used in JS.
315+
- classic const based types
316+
- very close to standard JS usage
317+
- more boilerplate
318+
- need to export action types and action creators to re-use in other layers like `redux-saga` or `redux-observable` modules
319+
- using `returntypeof()` helper function to infer return type of Action Creators - (https://github.com/piotrwitek/react-redux-typescript#returntypeof-polyfill)
320320

321321
```ts
322322
import { returntypeof } from 'react-redux-typescript';
@@ -360,14 +360,12 @@ store.dispatch(actionCreators.changeBaseCurrency('USD')); // { type: "CHANGE_BAS
360360

361361
```
362362
363-
### DRY Approach
364-
- less boilerplate
365-
- minimize repeated code
366-
- `ActionCreator` helper factory function to create typed instances of actions - (https://github.com/piotrwitek/react-redux-typescript#helpers-v22)
367-
- easier to use in multiple reducer files or `redux-observable` modules (action creators have type property and also create function, no extra type constant)
368-
- very easy to get all of action types
363+
### DRY Solution
364+
This solution is using a simple helper factory function to automate creation of typed action creators. With little abstraction we can reduce boilerplate and code repetition, also it is easier to re-use action creators in other layers:
369365
370-
This case is using a helper factory function to create typed actions. With little abstraction we can significally reduce boilerplate and code repetition, also it is easier to re-use action creators in other reducer or redux-observable modules.
366+
- using helper factory function to automate creation of typed action creators - recommended battle-tested `ActionCreator` from (https://github.com/piotrwitek/react-redux-typescript#helpers-v22)
367+
- reduced boilerplate and code repetition
368+
- easier to re-use in other layers like `redux-saga` or `redux-observable` modules (action creators have type property and also create function, no extra type constant)
371369
372370
```ts
373371
import { ActionCreator } from 'react-redux-typescript';
@@ -403,9 +401,9 @@ export default function reducer(state: State = initialState, action: Action): St
403401
- using Partial from (Mapped types)[https://www.typescriptlang.org/docs/handbook/advanced-types.html]
404402
- to guard type of `partialState` and restrict superfluous or mismatched props when merging with State
405403
406-
### Switch Approach
404+
### Switch Style
407405
- using classic const based types
408-
- good enough for single prop updates
406+
- good for single prop updates or simple state objects
409407
410408
```ts
411409
// State
@@ -434,9 +432,9 @@ export default function reducer(state: State = initialState, action: Action): St
434432
```
435433
436434
### If Approach
437-
- using `ActionCreator` helper types
438-
- much better for multiple props update as it will ensure you that `Partial State` update is compatible with reducer state contract, this will guard you from errors
439-
- if's "block scope" give you possibility to use local variables
435+
- if's "block scope" give you possibility to use local variables for more complex state update logic
436+
- better for more complex state objects - using partialState object spread for strongly typed multiple props update - it will ensure that action payload is compatible with reducer state contract - this will guard you from nasty bugs
437+
- introducing optional static `type` property on `actionCreator` - advantage is to get rid of action types constants, as you can check type on action creator itself
440438
441439
```ts
442440
// State
@@ -453,10 +451,10 @@ export const initialState: State = {
453451
export default function reducer(state: State = initialState, action: Action): State {
454452
let partialState: Partial<State> | undefined;
455453

456-
if (action.type === ActionCreators.IncreaseCounter.type) {
457-
partialState = { counter: action.payload }; // number
454+
if (action.type === actionCreators.increaseCounter.type) {
455+
partialState = { counter: state.counter + 1 }; // no payload
458456
}
459-
if (action.type === ActionCreators.ChangeBaseCurrency.type) {
457+
if (action.type === actionCreators.changeBaseCurrency.type) {
460458
partialState = { baseCurrency: action.payload }; // string
461459
}
462460

@@ -467,10 +465,54 @@ export default function reducer(state: State = initialState, action: Action): St
467465
---
468466
469467
## Async Flow
470-
- WIP
468+
- `redux-observable` epics
471469
472470
```ts
473-
471+
import 'rxjs/add/operator/map';
472+
import { combineEpics, Epic } from 'redux-observable';
473+
474+
import { RootState, Action } from '../index'; // check store section
475+
import { actionCreators } from './reducer';
476+
import { convertValueWithBaseRateToTargetRate } from './utils';
477+
import * as currencyConverterSelectors from './selectors';
478+
import * as currencyRatesSelectors from '../currency-rates/selectors';
479+
480+
// Epics - handling side effects of actions
481+
const changeCurrencyEpic: Epic<Action, RootState> = (action$, store) =>
482+
action$.ofType(
483+
actionCreators.changeBaseCurrency.type,
484+
actionCreators.changeTargetCurrency.type,
485+
).map((action): Action => actionCreators.updateCurrencyConverterState({
486+
targetValue: convertValueWithBaseRateToTargetRate(
487+
currencyConverterSelectors.getBaseValue(store.getState()),
488+
currencyRatesSelectors.getBaseCurrencyRate(store.getState()),
489+
currencyRatesSelectors.getTargetCurrencyRate(store.getState()),
490+
),
491+
}));
492+
493+
const changeBaseValueEpic: Epic<Action, RootState> = (action$, store) =>
494+
action$.ofType(actionCreators.changeBaseValue.type)
495+
.map((action): Action => actionCreators.updateCurrencyConverterState({
496+
targetValue: convertValueWithBaseRateToTargetRate(
497+
action.payload,
498+
currencyRatesSelectors.getBaseCurrencyRate(store.getState()),
499+
currencyRatesSelectors.getTargetCurrencyRate(store.getState()),
500+
),
501+
}));
502+
503+
const changeTargetValueEpic: Epic<Action, RootState> = (action$, store) =>
504+
action$.ofType(actionCreators.changeTargetValue.type)
505+
.map((action): Action => actionCreators.updateCurrencyConverterState({
506+
baseValue: convertValueWithBaseRateToTargetRate(
507+
action.payload,
508+
currencyRatesSelectors.getTargetCurrencyRate(store.getState()),
509+
currencyRatesSelectors.getBaseCurrencyRate(store.getState()),
510+
),
511+
}));
512+
513+
export const epics = combineEpics(
514+
changeCurrencyEpic, changeBaseValueEpic, changeTargetValueEpic,
515+
);
474516
```
475517
476518
---
@@ -486,22 +528,29 @@ export default function reducer(state: State = initialState, action: Action): St
486528
487529
## Store & RootState
488530
489-
`RootState` - to be imported in connected components providing type safety to Redux `connect` function
490531
```ts
491532
import {
492-
default as currencyRatesReducer, State as CurrencyRatesState,
533+
default as currencyRatesReducer, State as CurrencyRatesState, Action as CurrencyRatesAction,
493534
} from './currency-rates/reducer';
494535
import {
495-
default as currencyConverterReducer, State as CurrencyConverterState,
536+
default as currencyConverterReducer, State as CurrencyConverterState, Action as CurrencyConverterAction,
496537
} from './currency-converter/reducer';
497538

539+
// - strongly typed application global state tree - `RootState`
540+
// - should be imported in connected components providing type safety to Redux `connect` function
498541
export type RootState = {
499542
currencyRates: CurrencyRatesState;
500543
currencyConverter: CurrencyConverterState;
501544
};
545+
546+
// - strongly typed application global action types - `Action`
547+
// - should be imported in layers dealing with redux actions like: reducers, redux-sagas, redux-observables
548+
export type Action =
549+
CurrencyRatesAction
550+
| CurrencyConverterAction;
502551
```
503552
504-
Use `RootState` in `combineReducers` function and as rehydrated State object type guard to obtain strongly typed Store instance
553+
- creating store - use `RootState` (in `combineReducers` or when providing preloaded state object) to set-up *state object type guard* to leverage strongly typed Store instance
505554
```ts
506555
import { combineReducers, createStore } from 'redux';
507556

@@ -519,6 +568,28 @@ export const store = createStore(
519568
);
520569
```
521570
571+
- composing enhancers - example of setting up `redux-observable` middleware
572+
```ts
573+
declare var window: Window & { devToolsExtension: any, __REDUX_DEVTOOLS_EXTENSION_COMPOSE__: any };
574+
import { createStore, compose, applyMiddleware } from 'redux';
575+
import { combineEpics, createEpicMiddleware } from 'redux-observable';
576+
577+
import { epics as currencyConverterEpics } from './currency-converter/epics';
578+
579+
const rootEpic = combineEpics(
580+
currencyConverterEpics,
581+
);
582+
const epicMiddleware = createEpicMiddleware(rootEpic);
583+
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
584+
585+
// store singleton instance
586+
export const store = createStore(
587+
rootReducer,
588+
recoverState(),
589+
composeEnhancers(applyMiddleware(epicMiddleware)),
590+
);
591+
```
592+
522593
---
523594
524595
# Common

0 commit comments

Comments
 (0)