Skip to content
This repository was archived by the owner on Feb 3, 2020. It is now read-only.

Commit 407dadd

Browse files
committed
feat(create-async-actions): add createAsyncActions utility function
1 parent c84781f commit 407dadd

File tree

6 files changed

+136
-2
lines changed

6 files changed

+136
-2
lines changed

README.md

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ see [running on CodeSandbox][cs].
8888

8989
## API
9090

91-
This package exports a grand total of three functions.
91+
This package exports a grand total of four functions.
9292

9393
A lot of the generics for these functions can be inferred (see above example).
9494
The typings below provided are optimized for readability.
@@ -155,6 +155,39 @@ The `reduceReducers` function takes an array of reducer functions and an
155155
optional initial state value and returns a single reducer which runs all of the
156156
input reducers in sequence.
157157

158+
### `createAsyncActions<T, A extends any[], ...>(type: string, startPayloadCreator, successPayloadCreator, failPayloadCreator)`
159+
160+
Oftentimes when working with sagas, thunks, or some other asynchronous,
161+
side-effecting middleware you need to create three actions which are named
162+
similarly. This is a convenience function which calls `createAction` three
163+
times for you. Consider the following example:
164+
165+
```ts
166+
import { noop } from 'lodash';
167+
import { createAsyncActions } from 'redux-ts-utils';
168+
169+
type User = { name: string };
170+
171+
export const [
172+
requestUsers,
173+
requestUsersSuccess,
174+
requestUsersFailure,
175+
] = createAsyncActions('REQUEST_USERS', noop, (users: User[]) => users);
176+
177+
requestUsers(); // returns action of type `REQUEST_USERS`
178+
requestUsersSuccess([{ name: 'knpwrs' }]); // returns action of type `REQUEST_USERS/SUCCESS`
179+
requestUsersError(); // returns action of type `REQUEST_USERS/ERROR`
180+
```
181+
182+
The first argument is the action/triad name, and the second through third
183+
(optional) arguments are payload creators for the initial action, the success
184+
action, and the error action, respectively. `noop` is imported from lodash in
185+
order to be explicit that in this case the payload for `requestUsers` is
186+
`void`. You can just as easily use `() => {}` inline. The action creators infer
187+
their payload types from the supplied payload creators. See [the
188+
implementation](./src/create-async-actions.ts) for complete type information.
189+
190+
158191
## Design Philosophy
159192

160193
### A Strong Emphasis on Type Safety

src/create-action.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ export interface TsActionCreator<P = void, A extends any[] = [P], M = void> {
1313
}
1414

1515
export type PayloadCreator<P, A extends any[] = [P?]> = (...args: A) => P;
16-
const identity = <T extends any[]>(...arg: T): T[0] => arg[0];
16+
export const identity = <T extends any[]>(...arg: T): T[0] => arg[0];
1717

1818
// eslint-disable-next-line arrow-parens
1919
export default <P, A extends any[] = [P?], M = void>(

src/create-async-actions.test.ts

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import createAsyncActions from './create-async-actions';
2+
3+
test('creates a triad of identity action creators', () => {
4+
const [start, success, fail] = createAsyncActions<string>('foo');
5+
expect(start.type).toBe('foo');
6+
expect(success.type).toBe('foo/SUCCESS');
7+
expect(fail.type).toBe('foo/ERROR');
8+
9+
expect(start('foo')).toEqual({
10+
type: 'foo',
11+
payload: 'foo',
12+
});
13+
14+
expect(success('foo')).toEqual({
15+
type: 'foo/SUCCESS',
16+
payload: 'foo',
17+
});
18+
19+
const err = new Error('foo');
20+
expect(fail(err)).toEqual({
21+
type: 'foo/ERROR',
22+
payload: err,
23+
error: true,
24+
});
25+
});
26+
27+
test('creates a triad of action creators with custom payloads', () => {
28+
const [start, success, fail] = createAsyncActions(
29+
'bar',
30+
(str: string) => str,
31+
(length: number) => length,
32+
);
33+
expect(start.type).toBe('bar');
34+
expect(success.type).toBe('bar/SUCCESS');
35+
expect(fail.type).toBe('bar/ERROR');
36+
37+
expect(start('bar')).toEqual({
38+
type: 'bar',
39+
payload: 'bar',
40+
});
41+
42+
expect(success(3)).toEqual({
43+
type: 'bar/SUCCESS',
44+
payload: 3,
45+
});
46+
47+
const err = new Error('foo');
48+
expect(fail(err)).toEqual({
49+
type: 'bar/ERROR',
50+
payload: err,
51+
error: true,
52+
});
53+
});
54+
55+
test('allows for mixed void and any', () => {
56+
const [start, success, fail] = createAsyncActions(
57+
'baz',
58+
() => {},
59+
(users: { name: string }[]) => users,
60+
);
61+
62+
expect(start()).toEqual({
63+
type: 'baz',
64+
});
65+
expect(success([{ name: 'knpwrs' }])).toEqual({
66+
type: 'baz/SUCCESS',
67+
payload: [{ name: 'knpwrs' }],
68+
});
69+
const err = new Error('baz');
70+
expect(fail(err)).toEqual({
71+
type: 'baz/ERROR',
72+
payload: err,
73+
error: true,
74+
});
75+
});

src/create-async-actions.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import createAction, { PayloadCreator, TsActionCreator, identity } from './create-action';
2+
3+
export default <
4+
PStart,
5+
AStart extends any[] = [PStart],
6+
PSuc = PStart,
7+
ASuc extends any[] = AStart,
8+
PErr = Error,
9+
AErr extends any[] = [PErr]
10+
>(
11+
name: string,
12+
startPc: PayloadCreator<PStart, AStart> = identity,
13+
sucPc: PayloadCreator<PSuc, ASuc> = identity,
14+
errPc: PayloadCreator<PErr, AErr> = identity,
15+
): [
16+
TsActionCreator<PStart, AStart>,
17+
TsActionCreator<PSuc, ASuc>,
18+
TsActionCreator<PErr, AErr>,
19+
] => [
20+
createAction<PStart, AStart>(name, startPc),
21+
createAction<PSuc, ASuc>(`${name}/SUCCESS`, sucPc),
22+
createAction<PErr, AErr>(`${name}/ERROR`, errPc),
23+
];

src/index.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
import createAction from './create-action';
2+
import createAsyncActions from './create-async-actions';
23
import handleAction from './handle-action';
34
import reduceReducers from './reduce-reducers';
45
import * as mod from '.';
56

67
test('module exports', () => {
78
expect(mod).toEqual({
89
createAction,
10+
createAsyncActions,
911
handleAction,
1012
reduceReducers,
1113
});

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ export {
44
TsAction,
55
TsActionCreator,
66
} from './create-action';
7+
export { default as createAsyncActions } from './create-async-actions';
78
export {
89
default as handleAction,
910
Draft,

0 commit comments

Comments
 (0)