Redux-Toolbelt
A set of tools for quicker, easier, less verbose and safer redux development by welldone-software.
Written in ES6.
TOC
Installation
The tools are available in the redux-toolbelt
npm package.
npm install --save redux-toolbelt
yarn add redux-toolbelt
Usage
import the functions you like to use using one of the two methods:
import {composeReducers, makeActionCreator} from 'redux-toolbelt'
import composeReducers from 'redux-toolbelt/lib/composeReducers'
import makeActionCreator from 'redux-toolbelt/lib/makeActionCreator'
Demo
A demo project can be found here:
https://github.com/welldone-software/redux-toolbelt-demo
The demo can be run in a live sandbox environment here:
https://codesandbox.io/s/github/welldone-software/redux-toolbelt-demo
API Reference
composeReducers()
This function in a replacement for redux's combineReducers
.
The function excepts multiple reducers as arguments and executes them one after the other.
If in argument is a reducers map instead of a reducer (like in composeReducers
) we create a reducer from that map in the same fashion.
const mainReducer = (state, action) => {...}
const sideEffectReducer = (state, action) => {...}
const currentActionReducer = (state, action) => {...}
const userNameReducer = (state, action) => {...}
const reducer = composeReducers(
mainReducer,
sideEffectReducer,
{
currentAction: currentActionReducer,
userName: userNameReducer
}
)
Default State
As a result of the reducers run one after the other, only the first one will get an undefined
state on the first run.
Nested reducers will get undefined
if none was supplied in the root state:
const DEFAULT_STATE = {val: 1}
const IGNORED = {anotherVal: 2}
const NESTED_DEFAULT_STATE = 5
const mainReducer = (state = DEFAULT_STATE, action) => {...}
const anotherReducer = (state = IGNORED_STATE, action) => {...}
const nestedReducer = (state = NESTED_DEFAULT_STATE, action) => {...}
const reducer = composeReducers(
mainReducer,
anotherReducer,
{
nestedVal: nestedReducer
}
)
makeActionCreator()
Create an FSA complient action creator that exposes its TYPE
as static member.
This can help force type-safty without adding alot of verbose code and constants.
All produced actions will have a type
, payload
and meta
properties.
const myAction = makeActionCreator('MY_ACTION')
const myReducer = (state, action) => {
switch (action.type) {
case myAction.TYPE:
return newState
default:
return state
}
}
Adding payload and metadata to actions
The actions creators excepts payload
and meta
as argument by default.
myAction({val: 5}, {debug: true})
To customize the action creators the make them more clear you can use the second parameter argMapper
.
const myAction = makeActionCreator('MY_ACTION', (val, debug=false) => ({
payload: {val},
meta: {debug}
}))
myAction(5, true)
Actions Defaults
There are situations where you want to creates actions that has logical relations with each other with a prefix, or a common default metadata.
You can do it like so:
const makeCounterAction = makeActionCreator.withDefaults({
prefix: "COUNTER/",
defaultMeta: {log: true}
})
const increase = makeCounterAction('INCREASE')
const decrease = makeCounterAction('DECREASE')
increase()
decrease()
makeReducer()
Creates a reducer that handles action created with makeActionCreator()
.
The second parameter is the handler of the specified action.
If it is not supplied, the reducer will always return the payload of the action.
The last parameter (second or third) is options.
It can only have one option right now: defaultState
that specifies the initial state. It is null
by default.
const toggle = makeActionCreator('TOGGLE')
const visibilityState = makeReducer(toggleActionCreatora, visible => !visible, {defaultState: true})
const state1 = reducer(undefined, {TYPE: '@@redux/INIT'})
// state1 === true
const state2 = reducer(state1, toggle())
// state2 === false
const state3 = reducer(state2, toggle())
// state3 === true
It is very useful with composeReducers
:
const setUserName = makeActionCreator('SET_USER_NAME')
const toggleShow = makeActionCreator('TOGGLE_SHOW')
const reducer = composeReducers({
userName: makeReducer(setUserName),
show: makeReducer(toggleShow, state => !state, {defaultState: true}),
})
const initialState = reducer(undefined, {type: '@@redux/INIT'})
// initialState ==> {
// userName: null,
// show: true,
// }
const state1 = reducer(initialState, setUserName('test-user-name'))
// state1 ==> {
// userName: 'test-user-name',
// show: true,
// }
const state3 = reducer(state2, toggleShow())
// state3 ==> {
// userName: 'test-user-name',
// show: false,
// }
makeAsyncActionCreator()
Wrapper around makeActionCreator()
, to help create multiple actions creators for usage in async/side effects middlewares like redux-thunk
, redux-saga
or redux-observable
.
const fetchTodos = makeAsyncActionCreator('FETCH_TODOS')
fetchTodos()
fetchTodos.success()
fetchTodos.failure()
fetchTodos.progress()
fetchTodos.cancel()
const myReducer = (state, action) => {
switch (action.type) {
case fetchTodos.TYPE:
return newState
case fetchTodos.success.TYPE:
return newState
case fetchTodos.failure.TYPE:
return newState
case fetchTodos.progress.TYPE:
return newState
case fetchTodos.cancel.TYPE:
return newState
default:
return state
}
}
makeAsyncReducer()
Creates a reducer that handles action created with makeAsyncActionCreator()
.
Behavior can be defined in an options object passed as the 2nd arg:
const asyncAction = makeAsyncActionCreator('ASYNC_ACTION')
const options = {
dataProp: 'data',
shouldDestroyData: true,
defaultData: undefined,
shouldSpread: false,
shouldSetData: true
}
const asyncReducer = makeAsyncReducer(asyncAction, options)
Reducer Behvaiour
Reducers created with makeAsyncReducer()
respond to the request, progree, success and failure actions.
Initialization
On start, the reducer will return the following state by default:
const asyncReducer = makeAsyncReducer(asyncAction)
const state = undefined
asyncReducer(state, {type: '@@INIT'})
You can customize the data
field name or default value.
const asyncReducer = makeAsyncReducer(asyncAction, {
dataProp: 'results',
defaultData: []
})
const state = undefined
asyncReducer(state, {type: '@@INIT'})
You can remove the use of the dataProp
.
const asyncReducer = makeAsyncReducer(asyncAction, {
shouldSpread: true,
defaultData: {
counter: 0,
status: 'offline'
}
})
const state = undefined
asyncReducer(state, {type: '@@INIT'})
Request
When the reducer gets the request
action it updates the loading
field.
const asyncReducer = makeAsyncReducer(asyncAction)
const state = {loading: false, data: [1, 2, 3]}
asyncReducer(state, asyncAction())
You can also configure the reducer to destory the current results.
const asyncReducer = makeAsyncReducer(asyncAction, {
shouldDestroyData: true,
defaultData: []
})
const state = {loading: false, data: [1, 2, 3]}
asyncReducer(state, asyncAction())
Progress
When the reducer gets the progress
action is updates the progress
field with the action's payload.
const asyncReducer = makeAsyncReducer(asyncAction)
const state = {loading: true}
asyncReducer(state, asyncAction.progress(5))
Success
When the reducer gets the success
action is updates the loading
to true
and sets the dataProp
field with the action's payload.
const asyncReducer = makeAsyncReducer(asyncAction)
const state = {loading: true}
asyncReducer(state, asyncAction.success([1, 2, 3]))
If the data isn't needed you can remove it from the state completely.
In this way you only detect requests success and failure.
const asyncReducer = makeAsyncReducer(asyncAction, {
shouldSetData: false
})
const state = {loading: true}
asyncReducer(state, asyncAction.success([1, 2, 3]))
Failure
When the reducer gets the failure
action is updates the loading
to false
and the error
field with the action's payload.
const asyncReducer = makeAsyncReducer(asyncAction)
const state = {loading: true}
asyncReducer(state, asyncAction.failure(`Server unreachable`))