Skip to content

Commit

Permalink
Merge pull request #4 from Alexxzz/expose-action-type
Browse files Browse the repository at this point in the history
#3 Expose action type name
  • Loading branch information
esamattis authored Dec 2, 2018
2 parents c3eefbd + e11d461 commit 7236645
Show file tree
Hide file tree
Showing 4 changed files with 60 additions and 10 deletions.
24 changes: 22 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ expect(store.getState().firstName).toEqual("Charlie");
expect(store.getState().lastName).toEqual("Brown");
```

Under the hood the class is desconstructed to following actions:
Under the hood the class is deconstructed to following actions:

```js
{
Expand All @@ -88,11 +88,31 @@ Under the hood the class is desconstructed to following actions:

So the method names become the Redux Action Types and the method arguments
become the action payloads. The reducer function will then match these
actions against the class and calls the approciate methods with the payload
actions against the class and calls the appropriate methods with the payload
array spread to the arguments. But do note that the action format is not part of
the public API so don't write any code relying on it. The actions are handled
by the generated reducer function.

If there is a need for some reason to access to the action type name, for example to
integrate with side effects libraries such as [redux-observable](https://github.com/redux-observable/redux-observable/) or [redux-saga](https://github.com/redux-saga/redux-saga),
you can access it using `type` property of the action creator function:
```ts
// Get the action name to subscribe to
const setFirstNameActionTypeName = ActionCreators.setFirstName.type;

// Get the action type to have a type safe Epic
type SetFirstNameAction = typeof ReturnType<ActionCreators.setFirstName>;

const setFirstNameEpic: Epic<SetFirstNameAction> = action$ =>
action$
.ofType(setFirstNameActionTypeName)
.pipe(
// action.payload - recognized as string
map(action => action.payload.toUpperCase()),
...
);
```

The generated reducer function executes the methods inside the `produce()`
function of Immer enabling the terse mutatable style updates.

Expand Down
3 changes: 3 additions & 0 deletions __dtslint__/immer-reducer.dtslint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,3 +146,6 @@ const OtherActionCreators = createActionCreators(OtherReducer);
// Mixed reducer and action creators from different ImmerReducer classes
// $ExpectError
reducer({foo: "sdf", bar: 2}, OtherActionCreators.setDing("sdf"));

// Action creator provides action type
const actionType: "setBar" = ActionCreators.setBar.type;
16 changes: 16 additions & 0 deletions __tests__/immer-reducer.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -313,3 +313,19 @@ test("throw error when using duplicate customNames", () => {
createReducerFunction(Reducer2);
}).toThrow();
});

test("action creators expose the actual action type name", () => {
const initialState = {foo: "bar"};

class TestReducer extends ImmerReducer<typeof initialState> {
setBar(foo: string) {
this.draftState.foo = foo;
}
}

const ActionCreators = createActionCreators(TestReducer);

expect(ActionCreators.setBar.type).toEqual(
"IMMER_REDUCER:TestReducer#setBar",
);
});
27 changes: 19 additions & 8 deletions src/immer-reducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,14 +49,22 @@ interface ImmerReducerFunction<T extends ImmerReducerClass> {
): ImmerReducerState<T>;
}

/** ActionCreator function interface with actual action type name */
interface ActionCreator<ActionTypeType, Payload extends any[]> {
readonly type: ActionTypeType;

(...args: Payload): {
type: ActionTypeType;
payload: Payload;
};
}

/** generate ActionCreators types from the ImmerReducer class */
export type ActionCreators<ClassActions extends ImmerReducerClass> = {
[K in keyof Methods<InstanceType<ClassActions>>]: (
...args: ArgumentsType<InstanceType<ClassActions>[K]>
) => {
type: K;
payload: ArgumentsType<InstanceType<ClassActions>[K]>;
}
[K in keyof Methods<InstanceType<ClassActions>>]: ActionCreator<
K,
ArgumentsType<InstanceType<ClassActions>[K]>
>
};

/** The actual ImmerReducer class */
Expand Down Expand Up @@ -94,12 +102,15 @@ export function createActionCreators<T extends ImmerReducerClass>(
return;
}

actionCreators[key] = (...args: any[]) => {
const type = `${PREFIX}:${getReducerName(immerReducerClass)}#${key}`;
const actionCreator = (...args: any[]) => {
return {
type: `${PREFIX}:${getReducerName(immerReducerClass)}#${key}`,
type,
payload: args,
};
};
actionCreator.type = type;
actionCreators[key] = actionCreator;
});

return actionCreators as any; // typed in the function signature
Expand Down

0 comments on commit 7236645

Please sign in to comment.