Skip to content

Commit

Permalink
fix: type-safety of action creators
Browse files Browse the repository at this point in the history
- enforce correct number of arguments using fuction overloads
- update README

closes knpwrs#27 and knpwrs#28
  • Loading branch information
Mikolaj Klaman committed May 21, 2019
1 parent 98001a1 commit 3bfedf5
Show file tree
Hide file tree
Showing 3 changed files with 37 additions and 9 deletions.
12 changes: 9 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ import { createAction, handleAction, reduceReducers } from 'redux-ts-utils';

// Actions

const increment = createAction<void>('increment');
const decrement = createAction<void>('decrement');
const increment = createAction('increment');
const decrement = createAction('decrement');
const add = createAction<number>('add');
const override = createAction<number>('override');

Expand Down Expand Up @@ -62,7 +62,11 @@ store.dispatch(increment());
store.dispatch(increment());
store.dispatch(increment());
store.dispatch(decrement());
// store.dispatch(decrement(1)); <-- TypeError: Expected 0 arguments, but got 1.

store.dispatch(add(10));
// store.dispatch(add()); <-- TypeError: Expected 1 arguments, but got 0.

console.log('Final count!', store.getState().counter); // 12
```

Expand Down Expand Up @@ -104,7 +108,9 @@ properties: `type` (a `string`) and `payload` (typed as `T`).
Typically it is best to use the simplest signature for this function:

```ts
const myActionCreator = createAction<MyActionPayload>('MY_ACTION');
const myVoidActionCreator = createAction('MY_VOID_ACTION');

const myPayloadActionCreator = createAction<MyActionPayload>('MY_PAYLOAD_ACTION');
```

The action creator function will be typed to take whatever you provide as a
Expand Down
26 changes: 22 additions & 4 deletions src/create-action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,32 @@ export interface TsActionCreator<P = void, A extends any[] = [P], M = void> {
(...args: A): TsAction<P, M>;
type: string;
}
interface TsIdentityPayloadActionCreator<P> {
(payload: P): TsAction<P, void>;
type: string;
}
interface TsVoidActionCreator {
(): TsAction<void, void>;
type: string;
}

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

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

function createAction(type: string): TsVoidActionCreator;
function createAction<P>(type: string): TsIdentityPayloadActionCreator<P>;
function createAction<P, A extends any[] = [P?], M = void>(
type: string,
pc: PayloadCreator<P, A>,
mc?: PayloadCreator<M, A>,
): TsActionCreator<P, A, M>;

function createAction <P, A extends any[] = [P?], M = void>(
type: string,
pc: PayloadCreator<P, A> = identity,
mc?: PayloadCreator<M, A>,
): TsActionCreator<P, A, M> => {
): TsVoidActionCreator | TsIdentityPayloadActionCreator<P> | TsActionCreator<P, A, M> {
// Continue with creating an action creator
const ac = (...args: A): TsAction<P, M> => {
const payload = pc(...args);
Expand All @@ -42,4 +58,6 @@ export default <P, A extends any[] = [P?], M = void>(
ac.toString = () => type;

return ac as TsActionCreator<P, A, M>;
};
}

export default createAction;
8 changes: 6 additions & 2 deletions src/example.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import { createAction, handleAction, reduceReducers } from '.';

// Actions

const increment = createAction<void>('increment');
const decrement = createAction<void>('decrement');
const increment = createAction('increment');
const decrement = createAction('decrement');
const add = createAction<number>('add');
const override = createAction<number>('override');

Expand Down Expand Up @@ -46,7 +46,11 @@ store.dispatch(increment());
store.dispatch(increment());
store.dispatch(increment());
store.dispatch(decrement());
// store.dispatch(decrement(1)); <-- TypeError: Expected 0 arguments, but got 1.

store.dispatch(add(10));
// store.dispatch(add()); <-- TypeError: Expected 1 arguments, but got 0.

console.log('Final count!', store.getState().counter);

assert(store.getState().counter === 12);

0 comments on commit 3bfedf5

Please sign in to comment.