Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/smeijer/redux-define

Define action constants for Redux
https://github.com/smeijer/redux-define

Last synced: 17 days ago
JSON representation

Define action constants for Redux

Awesome Lists containing this project

README

        

# redux-define

[![Join the chat at https://gitter.im/smeijer/redux-define](https://badges.gitter.im/smeijer/redux-define.svg)](https://gitter.im/smeijer/redux-define?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)

[![build status](https://img.shields.io/travis/smeijer/redux-define/master.svg?style=flat-square)][1]

[![NPM](https://nodei.co/npm/redux-define.png?downloads=true)][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 `` 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