redux-tools
v3.2.0
Published
Redux Tools to make redux easy
Downloads
2,300
Readme
Redux Tools
Redux Tools to make redux easy and reduce boilerplate with useful tools and conventions.
Contents
Getting Started
Please view the example folder to get a quick introduction or continue reading.
Models
The Problem
Does this folder structure look familiar to you?
src/
actions/
index.js
products.js
user.js
components/
App.js
Header.js
Product.js
reducers/
index.js
products.js
user.js
selectors/
index.js
products.js
user.js
types/
index.js
products.js
user.js
index.js
store.js
This is the basic organization shown in many different redux tutorials and examples. This works great until, one day, it doesn't. It becomes cumbersome to make changes across multiple folders when you're only really updating one thing. This is where models come in.
The Solution
The solution is to place things together by domain rather than type. All fetching, updating, and calculating for a domain lives inside one model. This includes actions, types, reducers, selectors, sagas. So that example app structure using models would look like this:
src/
components/
App.js
Header.js
Product.js
models/
products/
actions.js
index.js
reducer.js
sagas.js
selectors.js
user/
actions.js
index.js
reducer.js
sagas.js
selectors.js
index.js
index.js
store.js
Model Conventions
A model can be a folder with separate files for actions, reducer, selectors, etc. Or for simple models just a single file may be all you need (similar to Ducks in many ways). At the end of the day it will be an object with one or several of the following keys (each piece is optional):
- actions
- an object where each key is an action creator function
- reducer
- a reducer function for a branch of the state object, usually with the same name as the model itself
- sagas
- a single saga function that will be forked by the root reducer
- selectors
- an object of selector functions related to the model
- types
- an object of
SNAKE_CASE
keys andnamespace/SNAKE_CASE
values for use in reducers and sagas (and anywhere else you need them)
- an object of
Next let's look at each piece in a little more detail.
Actions and Types
This is what a typical definition for a type and action looks like:
const SET_FIRST_NAME = 'user/SET_FIRST_NAME';
const setFirstName = (payload) => ({
payload,
type: SET_FIRST_NAME,
});
And that gets even more repetitive when you start dealing with asynchronous actions:
const SAVE_REQUEST = 'user/SAVE_REQUEST';
const SAVE_SUCCESS = 'user/SAVE_SUCCESS';
const SAVE_FAILURE = 'user/SAVE_FAILURE';
const saveRequest = (payload) => ({
payload,
type: SAVE_REQUEST,
});
const saveSuccess = (payload) => ({
payload,
type: SAVE_SUCCESS,
});
const saveFailure = (payload) => ({
payload,
type: SAVE_FAILURE,
});
That repetition was a source of great angst for us so we thought there's a great opportunity for convention and tooling to reduce boilerplate.
The first step was to decide that since actions and types are so closely related anyway, why don't we explicitly couple them together?
Conventions for Actions and Types
- actions have a
type
and apayload
. Action creators will always accept a single parameter that will be placed onto thepayload
key of the action object. - types will be
SNAKE_CASE
with anamespace
of any case - action creators will have the
camelCase
version of the type'sSNAKE_CASE
value. - action creators will be generated automatically from types
So using our conventions and adding in redux-tools
tooling, we can greatly reduce the boilerplate for creating actions and types. Here's an equivalent of the above two examples:
import { Async, Basic } from 'redux-tools/types';
import { generateActionsAndTypes } from 'redux-tools';
const { actions, types } = generateActionsAndTypes('user', [
new Async('SAVE'),
new Basic('SET_FIRST_NAME'),
new Basic('SET_LAST_NAME'),
]);
Reducers
Model reducers are combined into the root reducer using Redux's combineReducers
function.
Sagas
Model sagas are combined by fork
ing each saga inside the root saga.
API Reference
Types
Use an implementation of the Type
class when defining your types. Redux Tools comes with two types defined out of the box: Async
and Basic
.
Basic
When used with generateActionsAndTypes
, this will result in a namespaced type and action creator.
import { Basic } from 'redux-tools/types';
import { generateActionsAndTypes } from 'redux-tools';
const { actions, types } = generateActionsAndTypes('user', [new Basic('SET_FIRST_NAME')]);
console.log(actions);
// { setFirstName: (payload) => ({ payload, type: 'user/SET_FIRST_NAME' }) }
console.log(types);
// { SET_FIRST_NAME: 'user/SET_FIRST_NAME' }
Async
When used with generateActionsAndTypes
, this will result in request, success, and failure action creators and types.
import { Async } from 'redux-tools/types';
import { generateActionsAndTypes } from 'redux-tools';
const { actions, types } = generateActionsAndTypes('user', [new Async('SAVE')]);
console.log(actions);
/*
{
saveRequest: (payload) => ({ payload, type: 'user/SAVE_REQUEST' }),
saveSuccess: (payload) => ({ payload, type: 'user/SAVE_SUCCESS' }),
saveFailure: (payload) => ({ payload, type: 'user/SAVE_FAILURE' }),
}
*/
console.log(types);
/*
types: {
SAVE_REQUEST: 'user/SAVE_REQUEST',
SAVE_SUCCESS: 'user/SAVE_SUCCESS',
SAVE_FAILURE: 'user/SAVE_FAILURE',
}
*/
generateActionsAndTypes
Arguments
namespace
(string)types
(array)
Returns
{
actions: {},
types: {},
}
Example
import { Async, Basic } from 'redux-tools/types';
import { generateActionsAndTypes } from 'redux-tools';
const { actions, types } = generateActionsAndTypes('user', [
new Async('SAVE'),
new Basic('SET_FIRST_NAME'),
new Basic('SET_LAST_NAME'),
]);
console.log(actions);
/*
{
saveRequest: (payload) => ({ payload, type: 'user/SAVE_REQUEST' }),
saveSuccess: (payload) => ({ payload, type: 'user/SAVE_SUCCESS' }),
saveFailure: (payload) => ({ payload, type: 'user/SAVE_FAILURE' }),
setFirstName: (payload) => ({ payload, type: 'user/SET_FIRST_NAME' }),
setLastName: (payload) => ({ payload, type: 'user/SET_LAST_NAME' }),
}
*/
console.log(types);
/*
types: {
SAVE_REQUEST: 'user/SAVE_REQUEST',
SAVE_SUCCESS: 'user/SAVE_SUCCESS',
SAVE_FAILURE: 'user/SAVE_FAILURE',
SET_FIRST_NAME: 'user/SET_FIRST_NAME',
SET_LAST_NAME: 'user/SET_LAST_NAME',
}
*/
models.create
Function to combine your different models for use in your app. Typically used in the index.js
file in your models directory.
Arguments
models
(object)
Returns
{
actions,
reducer,
sagas,
selectors,
types,
}
Example
import app from './app';
import lists from './lists';
import { models } from 'redux-tools';
import todos from './todos';
export const { actions, reducer, sagas, selectors, types } = models.create({
app,
lists,
todos,
});
console.log(actions);
/*
{
app: { ... },
lists: { ... },
todos: { ... },
}
*/
// Root reducer function to use in your redux store
console.log(reducer);
// Root saga generator function to pass to the saga middleware
console.log(sagas);
console.log(selectors);
/*
{
app: { ... },
lists: { ... },
todos: { ... },
}
*/
console.log(types);
/*
{
app: { ... },
lists: { ... },
todos: { ... },
}
*/
TODO
withErrorReporting: extend to allow raygun / crashalitics / error reporting system withRetry: extend to allow raygun / crashalitics / error reporting system