Skip to content

Commit 5278678

Browse files
committed
Demos generic thunk actions
1 parent c46a872 commit 5278678

File tree

3 files changed

+55
-58
lines changed

3 files changed

+55
-58
lines changed

src/actions/index.ts

Lines changed: 46 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,63 @@
1-
import * as redux from 'redux'
2-
31
import { api } from '../api'
2+
43
import * as state from '../reducers/index'
54

65
type Q<T> = { request: T }
76
type S<T> = { response: T }
87
type E = { error: Error }
98

10-
type QEmpty = Q<null>
9+
type QEmpty = Q<{}>
1110
type QValue = Q<{ value: number }>
1211

12+
export interface Dispatch<A> {
13+
(a: A): A
14+
}
15+
16+
type _T = Action['type']
17+
18+
type APIActionGroup<TQ extends _T, TS extends _T, TE extends _T, _Q, _S> =
19+
({ type: TQ } & Q<_Q>)
20+
| ({ type: TS } & Q<_Q> & S<_S>)
21+
| ({ type: TE } & Q<_Q> & E)
22+
23+
type Thunk<Q, S> = (request: Q) => Promise<S>
24+
25+
const createThunkAction = <Q, S, TQ extends _T, TS extends _T, TE extends _T>
26+
(fn: Thunk<Q, S>, tq: TQ, ts: TS, te: TE) =>
27+
(request: Q) =>
28+
(dispatch: Dispatch<APIActionGroup<TQ, TS, TE, Q, S>>) => {
29+
dispatch({ type: tq, request })
30+
fn(request)
31+
.then(response => dispatch({ type: ts, request, response }))
32+
.catch(error => dispatch({ type: te, request, error }))
33+
}
34+
35+
type LoadAction =
36+
({ type: 'LOAD_COUNT_REQUEST' } & QEmpty)
37+
| ({ type: 'LOAD_COUNT_SUCCESS' } & QEmpty & S<{ value: number }>)
38+
| ({ type: 'LOAD_COUNT_ERROR' } & QEmpty & E)
39+
40+
type SaveAction =
41+
({ type: 'SAVE_COUNT_REQUEST' } & QValue)
42+
| ({ type: 'SAVE_COUNT_SUCCESS' } & QValue & S<{}>)
43+
| ({ type: 'SAVE_COUNT_ERROR' } & QValue & E)
44+
1345
export type Action =
46+
LoadAction
47+
| SaveAction
1448
// UI actions
15-
{ type: 'INCREMENT_COUNTER', delta: number }
49+
| { type: 'INCREMENT_COUNTER', delta: number }
1650
| { type: 'RESET_COUNTER' }
1751

18-
// API Requests
19-
| ({ type: 'SAVE_COUNT_REQUEST' } & QValue)
20-
| ({ type: 'SAVE_COUNT_SUCCESS' } & QValue & S<{}>)
21-
| ({ type: 'SAVE_COUNT_ERROR' } & QValue & E)
52+
export const saveCount = createThunkAction(api.save,
53+
'SAVE_COUNT_REQUEST',
54+
'SAVE_COUNT_SUCCESS',
55+
'SAVE_COUNT_ERROR')
2256

23-
| ({ type: 'LOAD_COUNT_REQUEST' } & QEmpty)
24-
| ({ type: 'LOAD_COUNT_SUCCESS' } & QEmpty & S<{ value: number }>)
25-
| ({ type: 'LOAD_COUNT_ERROR' } & QEmpty & E)
57+
export const loadCount = createThunkAction(api.load,
58+
'LOAD_COUNT_REQUEST',
59+
'LOAD_COUNT_SUCCESS',
60+
'LOAD_COUNT_ERROR')
2661

2762
export const incrementCounter = (delta: number): Action => ({
2863
type: 'INCREMENT_COUNTER',
@@ -32,41 +67,3 @@ export const incrementCounter = (delta: number): Action => ({
3267
export const resetCounter = (): Action => ({
3368
type: 'RESET_COUNTER',
3469
})
35-
36-
export type ApiActionGroup<_Q, _S> = {
37-
request: (q?: _Q) => Action & Q<_Q>
38-
success: (s: _S, q?: _Q) => Action & Q<_Q> & S<_S>
39-
error: (e: Error, q?: _Q) => Action & Q<_Q> & E
40-
}
41-
42-
const _saveCount: ApiActionGroup<{ value: number }, {}> = {
43-
request: (request) =>
44-
({ type: 'SAVE_COUNT_REQUEST', request }),
45-
success: (response, request) =>
46-
({ type: 'SAVE_COUNT_SUCCESS', request, response }),
47-
error: (error, request) =>
48-
({ type: 'SAVE_COUNT_ERROR', request, error }),
49-
}
50-
51-
const _loadCount: ApiActionGroup<null, { value: number }> = {
52-
request: (request) =>
53-
({ type: 'LOAD_COUNT_REQUEST', request: null }),
54-
success: (response, request) =>
55-
({ type: 'LOAD_COUNT_SUCCESS', request: null, response }),
56-
error: (error, request) =>
57-
({ type: 'LOAD_COUNT_ERROR', request: null, error }),
58-
}
59-
60-
type apiFunc<Q, S> = (q: Q) => Promise<S>
61-
62-
function apiActionGroupFactory<Q, S>(x: ApiActionGroup<Q, S>, go: apiFunc<Q, S>) {
63-
return (request: Q) => (dispatch: redux.Dispatch<state.All>) => {
64-
dispatch(x.request(request))
65-
go(request)
66-
.then((response) => dispatch(x.success(response, request)))
67-
.catch((e: Error) => dispatch(x.error(e, request)))
68-
}
69-
}
70-
71-
export const saveCount = apiActionGroupFactory(_saveCount, api.save)
72-
export const loadCount = () => apiActionGroupFactory(_loadCount, api.load)(null)

src/api.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,13 @@ export type Api = {
2929

3030
export const api: Api = {
3131
save: (counter: Counter): Promise<null> => flakify(() => {
32-
localStorage.setItem('__counterValue', counter.value.toString())
33-
return null
34-
}),
32+
localStorage.setItem('__counterValue', counter.value.toString())
33+
return null
34+
}),
3535
load: (): Promise<Counter> => flakify(() => {
36-
const storedValue = parseInt(localStorage.getItem('__counterValue'), 10)
37-
return {
38-
value: storedValue || 0,
39-
}
40-
}),
36+
const storedValue = parseInt(localStorage.getItem('__counterValue'), 10)
37+
return {
38+
value: storedValue || 0,
39+
}
40+
}),
4141
}

src/components/counter.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ const mapDispatchToProps = (dispatch: redux.Dispatch<state.All>): ConnectedDispa
4343
increment: (n: number) =>
4444
dispatch(incrementCounter(n)),
4545
load: () =>
46-
dispatch(loadCount()),
46+
dispatch(loadCount({})),
4747
save: (value: number) =>
4848
dispatch(saveCount({ value })),
4949
})

0 commit comments

Comments
 (0)