redux-define
Version:
Define action constants for Redux
328 lines (247 loc) • 10.2 kB
Markdown
# redux-define
[](https://gitter.im/smeijer/redux-define?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[][1]
[][7]
## Installation
with npm:
```bash
npm install --save redux-define
```
or yarn:
```bash
yarn add redux-define
```
If you don’t use [npm][2], you may grab the latest [UMD][3] build from [unpkg][4]
(either a [development][5] or a [production][6] build). The UMD build exports a
global called `window.ReduxDefine` if you add it to your page via a `<script>` tag.
We *don’t* recommend UMD builds for any serious application, as most of the libraries
complementary to Redux are only available on [npm][8].
## Usage
### `defineAction(type, ?[subactions], ?namespace)`
```js
import { defineAction } from 'redux-define';
```
Create a redux action type with one or more subactions:
```js
const CREATE_TODO = defineAction('CREATE_TODO', ['ERROR', 'SUCCESS']);
// result:
console.log('' + CREATE_TODO); // CREATE_TODO
console.log('' + CREATE_TODO.ERROR); // CREATE_TODO_ERROR
console.log('' + CREATE_TODO.SUCCESS); // CREATE_TODO_SUCCESS;
```
Namespaces can be used to separate actions through out modules and apps.
```js
const CREATE_TODO = defineAction('CREATE_TODO', ['ERROR', 'SUCCESS'], 'my-app');
// result:
console.log('' + CREATE_TODO); // my-app/CREATE_TODO
console.log('' + CREATE_TODO.ERROR); // my-app/CREATE_TODO_ERROR
console.log('' + CREATE_TODO.SUCCESS); // my-app/CREATE_TODO_SUCCESS;
```
It's also possible to give in another constant as namespace for the new one.
```js
const todos = defineAction('todos', ['LOADING', 'SUCCESS'], 'my-app');
const CREATE_TODO = defineAction('CREATE_TODO', ['ERROR', 'SUCCESS'], todos);
// result:
console.log('' + CREATE_TODO); // my-app/todos/CREATE_TODO
console.log('' + CREATE_TODO.ERROR); // my-app/todos/CREATE_TODO_ERROR
console.log('' + CREATE_TODO.SUCCESS); // my-app/todos/CREATE_TODO_SUCCESS;
```
To integrate better with other `redux` libraries, a special `ACTION` property is
added to the constant. [`redux-actions`][9] and [`redux-saga`][11] for example
treat actionTypes other than `string` specially.
Extra benefit of this little feature, is that it makes the separation between
user actions and status updates more clear. Read more about this under
[best practice](#best-practice) and [integrations](#integrations)
```js
const CREATE_TODO = defineAction('CREATE_TODO', ['ERROR', 'SUCCESS']);
// result:
console.log('' + CREATE_TODO); // CREATE_TODO
console.log('' + CREATE_TODO.ACTION); // CREATE_TODO
console.log('' + CREATE_TODO.ERROR); // CREATE_TODO_ERROR
console.log('' + CREATE_TODO.SUCCESS); // CREATE_TODO_SUCCESS;
```
### `actionType.defineAction(type, ?[subactions])`
As alternative syntax, we can use the `defineAction` method on defined constants.
Constants defined in this way inherit their namespace. Making the namespace
argument obsolete.
```js
const myApp = defineAction('my-app');
const todos = myApp.defineAction('todos', ['LOADING', 'SUCCESS']);
const CREATE = todos.defineAction('CREATE', ['ERROR', 'SUCCESS']);
```
This is the same as writing:
```js
const myApp = defineAction('my-app');
const todos = defineAction('todos', ['LOADING', 'SUCCESS'], 'my-app');
const CREATE = todos.defineAction('CREATE', ['ERROR', 'SUCCESS'], todos);
```
Or if you only need the `CREATE` constant:
```js
const CREATE = todos.defineAction('CREATE', ['ERROR', 'SUCCESS'], 'my-app/todos');
```
Result in these cases is the same. Except in the third case, where we only defined
the `CREATE` constant:
```js
console.log('' + myApp); // my-app
console.log('' + todos); // my-app/todos
console.log('' + todos.LOADING); // my-app/todos_LOADING
console.log('' + todos.SUCCESS); // my-app/todos_SUCCESS
console.log('' + CREATE); // my-app/todos/CREATE
console.log('' + CREATE.ERROR); // my-app/todos/CREATE_ERROR;
console.log('' + CREATE.SUCCESS); // my-app/todos/CREATE_SUCCESS;
```
### Best practice
Extract general state constants into a separate file so they can easily be
imported and shared across different modules:
```js
// stateConstants.js
export const LOADING = 'LOADING';
export const ERROR = 'ERROR';
export const SUCCESS = 'SUCCESS';
```
```js
// app.js
export const myApp = defineAction('my-app');
```
In the module; we can import the `stateConstants` and optionally parent modules
to construct a namespace.
```js
// todos.js
import { defineAction } from 'redux-define';
import { LOADING, ERROR, SUCCESS } from './stateConstants';
import { myApp } from './app';
const todos = defineAction('todos', [LOADING, SUCCESS], myApp);
const CREATE = defineAction('CREATE', [ERROR, SUCCESS], todos);
// result:
console.log('' + myApp); // my-app
console.log('' + todos); // my-app/todos
console.log('' + todos.LOADING); // my-app/todos_LOADING
console.log('' + todos.SUCCESS); // my-app/todos_SUCCESS
console.log('' + CREATE); // my-app/todos/CREATE
console.log('' + CREATE.ACTION); // my-app/todos/CREATE
console.log('' + CREATE.ERROR); // my-app/todos/CREATE_ERROR
console.log('' + CREATE.SUCCESS); // my-app/todos/CREATE_SUCCESS
```
Use the `ACTION` constant in `dispatch` and in `saga watchers`. This makes it
clear that an user or system `ACTION` is being handled. All other subtypes
should be `status` updates. They should be handled trough `thunks` or `sagas`,
but never dispatched by a user. Although it is possible to handle user actions
in the reducer directly, the advice is to not do this. Keep clear separation
between user actions and reducer actions.
### Implementation example
##### stateConstants.js
```js
export const CANCELLED = 'CANCELLED';
export const ERROR = 'ERROR';
export const PENDING = 'PENDING';
export const SUCCESS = 'SUCCESS';
```
##### actionTypes.js
```js
import { defineAction } from 'redux-define';
import { CANCELLED, ERROR, PENDING, SUCCESS } from './stateConstants';
export const DELETE_COMMENT = defineAction('DELETE_COMMENT',
[CANCELLED, ERROR, PENDING, SUCCESS], 'comments');
```
##### actions.js
```js
import { createAction } from 'redux-actions';
import { DELETE_COMMENT } from './actionTypes';
export const deleteComment = createAction(DELETE_COMMENT.ACTION);
```
##### reducer.js
```js
import { handleActions, combineActions } from 'redux-actions';
import { DELETE_COMMENT } from './actionTypes';
const initialState = {
isDeleting: false,
};
const reducer = handleActions({
[DELETE_COMMENT.PENDING]: state => ({
...state,
isDeleting: true,
}),
[combineActions(
DELETE_COMMENT.CANCELLED,
DELETE_COMMENT.SUCCESS,
DELETE_COMMENT.ERROR,
)]: state => ({
...state,
isDeleting: false,
}),
}, initialState);
```
##### sagas.js
```js
import { call, put, take } from 'redux-saga/effects';
import deleteAPI from 'somewhere-out-of-this-scope';
import { DELETE_COMMENT } from './actionTypes';
export function* deleteComment({ payload }) {
try {
yield put({ type: DELETE_COMMENT.PENDING });
const { data } = yield call(deleteAPI, payload);
yield put({ type: DELETE_COMMENT.SUCCESS, payload: data });
}
catch (error) {
yield put({ type: DELETE_COMMENT.ERROR, payload: { error: error.message } });
}
}
```
##### watchers.js
```js
import { takeEvery } from 'redux-saga';
import { fork } from 'redux-saga/effects';
import { DELETE_COMMENT } from './actionTypes';
import * as s from './sagas';
function* deleteCommentWatcher() {
yield* takeEvery(DELETE_COMMENT.ACTION, s.deleteComment);
}
export default function* () {
yield [
fork(deleteCommentWatcher),
];
}
```
### Why use `redux-define`?
This library reduces a lot of the boilerplate that comes with defining redux
action types. This library is created as solution to [organizing large ducks][10]
Let's show the difference here. See above for a full [implementation example](#implementation-example).
When using `ducks`, some of the files in the example above should be joined into
a single duck file.
Without using `redux-define`
```js
const CREATE_TODO = 'CREATE_TODO';
const CREATE_TODO_PENDING = 'CREATE_TODO_PENDING';
const CREATE_TODO_ERROR = 'CREATE_TODO_ERROR';
const CREATE_TODO_SUCCESS = 'CREATE_TODO_SUCCESS';
const DELETE_TODO = 'DELETE_TODO';
const DELETE_TODO_PENDING = 'DELETE_TODO_PENDING';
const DELETE_TODO_CANCELLED = 'DELETE_TODO_CANCELLED';
const DELETE_TODO_ERROR = 'DELETE_TODO_ERROR';
const DELETE_TODO_SUCCESS = 'DELETE_TODO_SUCCESS';
```
With `redux-define`
```js
import { defineAction } from 'redux-define';
import { PENDING, CANCELLED, ERROR, SUCCESS } from '/lib/stateConstants.js';
const CREATE_TODO = defineAction('CREATE_TODO', [PENDING, ERROR, SUCCESS]);
const DELETE_TODO = defineAction('DELETE_TODO', [PENDING, CANCELLED, ERROR, SUCCESS]);
```
### Integrations
Created constants can be directly used in [`sagas`][11] `reducers`, or together
with [`redux-actions`][9].
See [implementation example](#implementation-example) in this readme for implementation
details. We handle [`redux-actions`][9] in [actions.js](#actionsjs) and
[reducer.js](#reducerjs) and [`redux-saga`][11] in [watchers.js](#watchersjs)
and [sagas.js](#sagasjs).
[1]: https://travis-ci.org/smeijer/redux-define
[2]: https://www.npmjs.com
[3]: https://unpkg.com/redux-define@latest/dist
[4]: https://unpkg.com
[5]: https://unpkg.com/redux-define@latest/dist/redux-define.js
[6]: https://unpkg.com/redux-define@latest/dist/redux-define.min.js
[7]: https://nodei.co/npm/redux-define
[8]: https://www.npmjs.com/search?q=redux
[9]: https://github.com/acdlite/redux-actions
[10]: https://github.com/erikras/ducks-modular-redux/issues/16
[11]: https://github.com/yelouafi/redux-saga