Skip to content

Commit

Permalink
Provide typing support for actions without payloads.
Browse files Browse the repository at this point in the history
Also, documenting a bit of Typescript learnings.
  • Loading branch information
Paul Sachs committed Aug 8, 2017
1 parent 230884b commit 83498c4
Show file tree
Hide file tree
Showing 3 changed files with 73 additions and 15 deletions.
48 changes: 48 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ You can also use a [browser friendly compiled file](https://unpkg.com/redux-act@
- [Adding and removing actions](#adding-and-removing-actions)
- [Async actions](#async-actions)
- [Enable or disable batch](#enable-or-disable-batch)
- [Typescript](#typescript)
- [Loggers](#loggers)
- [Redux Logger](#redux-logger)

Expand Down Expand Up @@ -748,6 +749,53 @@ reducer.options({ payload: false });
reducer.on(batch, (state, action) => action.payload.reduce(reducer, state));
```

### Typescript

We've built some basic typings around this API that will help Typescript identify potential issues in your code.

You can use any of the existing methods to create reducers and Typescript will work (as a superset of Javascript) but that kind of defeats some of the benefits
of Typescript. For this reason, the following is the recommended way to create a reducer.

```typescript
import { createReducer, createAction } from 'redux-act';

const defaultState = {
canCount: false,
canBounce: false,
count: 0
};

const setCanCount = createAction<boolean>('Set whether you can set count');

const reducer = createReducer<typeof defaultState>({}, defaultState);

reducer.on(setCanCount, (state, payload) => ({
...state,
canCount: payload
}));
```

Using the `reducer.on()` API, Typescript will identify the payload set on `setCanCount` and provide that type as payload. This can be really handy once
your code starts scaling up.

#### Caveats

Due to some limitations on the typescript typing system, our action creators have some limitations but you can create typed actions creators assuming you have no
payload reducer.

```typescript
import { createAction } from 'redux-act';

const action = createAction<boolean>('Some type');
const emptyAction = createAction('Another type');
const otherAction = createAction<boolean>('Other action', (arg1, arg2) => ({ arg1, arg2 }));
```

`action` and `emptyAction` will provide typing support, making sure `action` is provided a boolean as an arg, or `emptyAction` is not provided an argument at all.

`otherAction`, on the otherhand, will be able to be called with any arguments, regardless of what the payload reducer expects.


## Loggers

`redux-act` provides improved logging with some loggers, mostly for batched actions.
Expand Down
7 changes: 7 additions & 0 deletions test/typescript.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,13 @@ const simpleAct = createAction<boolean>('something');
// Simple actions provide validation on params.
const action = simpleAct(true);

const emptyAction = createAction('label');

// Empty actions don't have payload.
emptyAction();

// Invalid: emptyAction(null);

function onOff(on, off) {
on(act1, () => 1)
}
Expand Down
33 changes: 18 additions & 15 deletions types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,37 +31,40 @@ interface StoreWithDisbatch<S> extends Store<S> {

type StoreOrDispatch = Store<any> | Dispatch | Store<any>[] | Dispatch[]

// Action creators
interface ComplexActionCreator<P, M={}> {
(...args: any[]): Action<P, M>;
interface BaseActionCreator<T> {
getType(): string;

assignTo(arg: StoreOrDispatch): ComplexActionCreator<P, M>;
bindTo(arg: StoreOrDispatch): ComplexActionCreator<P, M>;

assigned(): boolean;
bound(): boolean;
dispatched(): boolean;

assignTo(arg: StoreOrDispatch): T;
bindTo(arg: StoreOrDispatch): T;
}

// Action creators
interface ComplexActionCreator<P, M={}> extends BaseActionCreator<ComplexActionCreator<P, M>> {
(...args: any[]): Action<P, M>;

raw(...args: any[]): Action<P, M>;
}

interface SimpleActionCreator<P, M={}> {
interface SimpleActionCreator<P, M={}> extends BaseActionCreator<SimpleActionCreator<P, M>> {
(payload: P): Action<P, M>;
getType(): string;

assignTo(arg: StoreOrDispatch): SimpleActionCreator<P, M>;
bindTo(arg: StoreOrDispatch): SimpleActionCreator<P, M>;
raw(payload: P): Action<P, M>;
}

assigned(): boolean;
bound(): boolean;
dispatched(): boolean;
interface EmptyActionCreator extends BaseActionCreator<EmptyActionCreator> {
(): Action<null, null>;

raw(payload: P): Action<P, M>;
raw(): Action<null, null>;
}

type ActionCreator<P, M={}> = SimpleActionCreator<P, M> | ComplexActionCreator<P, M>;
type ActionCreator<P, M={}> = SimpleActionCreator<P, M> | ComplexActionCreator<P, M> | EmptyActionCreator;

export function createAction(): EmptyActionCreator;
export function createAction(description: string): EmptyActionCreator;
export function createAction<P, M={}>(): SimpleActionCreator<P, M>;
export function createAction<P, M={}>(description: string): SimpleActionCreator<P, M>;
export function createAction<P, M={}>(description: string, payloadReducer: (...args: any[]) => P): ComplexActionCreator<P, M>;
Expand Down

0 comments on commit 83498c4

Please sign in to comment.