• Stars
    star
    174
  • Rank 219,104 (Top 5 %)
  • Language
    JavaScript
  • License
    MIT License
  • Created almost 8 years ago
  • Updated over 1 year ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Define action constants for Redux

redux-define

Join the chat at https://gitter.im/smeijer/redux-define

build status

NPM

Installation

with npm:

npm install --save redux-define

or yarn:

yarn add redux-define

If you don’t use npm, you may grab the latest UMD build from unpkg (either a development or a production 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.

Usage

defineAction(type, ?[subactions], ?namespace)

import { defineAction } from 'redux-define';

Create a redux action type with one or more subactions:

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.

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.

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 and redux-saga 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 and integrations

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.

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:

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:

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:

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:

// stateConstants.js
export const LOADING = 'LOADING';
export const ERROR = 'ERROR';
export const SUCCESS = 'SUCCESS';
// app.js
export const myApp = defineAction('my-app');

In the module; we can import the stateConstants and optionally parent modules to construct a namespace.

// 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
export const CANCELLED = 'CANCELLED';
export const ERROR     = 'ERROR';
export const PENDING   = 'PENDING';
export const SUCCESS   = 'SUCCESS';
actionTypes.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
import { createAction } from 'redux-actions';
import { DELETE_COMMENT } from './actionTypes';

export const deleteComment = createAction(DELETE_COMMENT.ACTION);
reducer.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
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
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 Let's show the difference here. See above for a full 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

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

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 reducers, or together with redux-actions.

See implementation example in this readme for implementation details. We handle redux-actions in actions.js and reducer.js and redux-saga in watchers.js and sagas.js.

More Repositories

1

unimported

Find and fix dangling files and unused dependencies in your JavaScript projects.
TypeScript
1,970
star
2

leaflet-geosearch

A geocoding/address-lookup library supporting various api providers.
TypeScript
1,037
star
3

next-runtime

All you need to handle POST requests, file uploads, and api requests, in Next.js getServerSideProps.
TypeScript
589
star
4

spin-delay

Smart spinner helper for React, to manage the duration of loading states.
JavaScript
182
star
5

where-broke

A CLI utility that helps finding breaking module versions using binary search and automated tests.
JavaScript
92
star
6

jest-partial

A partial matcher for Jest to simplify validation of complex structures.
JavaScript
20
star
7

latodoc

jsDoc3 Theme
JavaScript
19
star
8

graphql-args

A lib that parses the resolver ast, to return the requested object fields and provided params, at any nested level.
JavaScript
12
star
9

blocktober

say STOP to hacktoberfest spammers
JavaScript
11
star
10

paychecker

compare web developer salaries across the planet
TypeScript
5
star
11

issupported.com

Check if your browser is still supported by your favorite websites.
TypeScript
4
star
12

botz

TypeScript
4
star
13

open-api

TypeScript
3
star
14

git-prettier

Run prettier during git clean and smudge stages.
JavaScript
3
star
15

meijer.ws

MDX
2
star
16

cache-key

create stable cache keys from complex objects
TypeScript
2
star
17

smeijer

my ✨special ✨ profile repo
2
star
18

form-data-kit

a lib to parse, stringify, and expand FormData
TypeScript
2
star
19

geoxs-net

GeoXS website
CSS
1
star
20

eslint-config

JavaScript
1
star
21

meteor-prebuild

Compiler plugin to run node script prior to meteor build process
JavaScript
1
star
22

react-emotional

react-emotion extended with some plugins
JavaScript
1
star
23

fetch-addons

A collection of add-ons for the fetch API
TypeScript
1
star
24

something-new

whoei
TypeScript
1
star
25

nexis

CLI utils that help me bootstrap and manage my projects
TypeScript
1
star
26

fetch-repo

fetch a repo from github without git
TypeScript
1
star
27

sme-scripts

JavaScript
1
star
28

tsconfig

Shared TypeScript config for my projects
JavaScript
1
star
29

ts-project

JavaScript
1
star
30

link-workspaces

Link Yarn Workspaces while respecting publishConfig.directory
TypeScript
1
star
31

picoid

Nanoid configured to behave like Meteor Random.id
TypeScript
1
star
32

magicbell-react-nextjs

Next.js template for CodeSandbox Projects
TypeScript
1
star
33

gh-email

retrieve email address by github handle
JavaScript
1
star