🚧🚧🚧 Alpha 🚧🚧🚧
Quickly create bundles of action types, action creators, and reducers with a few lines of code.
I created boilerplate-reducer to reduce reducer boilerplate
When using redux, I found that I was writing a lot of code to create essentially the same types of objects. Even with great tools like redux-actions and reduce-reducers, and the guidelines for reducing boilerplate and combining reducer logic, I found that I was still writing a lot of similar code with minor differences. So I created boilerplate-reducer
to reduce reducer boilerplate.
Note: This is a helper and should not be a replacement for understanding the core concepts of Redux (e.g. that reducers can respond to any action types, even those that might be associated with other parts of the state tree). If you're new to Redux, come back after you've written some boilerplate code and gotten fed up!
- Make it easier to write commonly used redux code with a few lines and a little faith
- Never force the user into following the assumptions made by the library
- Allow the user to extend and customize what is created and make functions granular enough to work in other contexts
- Functions have no side effects (that's the whole reason we like Redux, right?)
- Be compatible with things like thunks, sagas, flowtype, immutable.js, etc. (eventually)
boilerplate-reducer
makes it easy to mash a lot of functions together into a bundle, but behind the scenes it makes use of several tried-and-tested tools for working with Redux. Action creators are generated with createAction(). Reducers are created with handleAction() and reduceReducers(). I recommend knowing a bit about what each one does. If When you need to extend the functionality of your app or run into issues, these tools will let you tweak things under the hood.
boilerplate-reducer
is an opinionated library. It makes assumptions about how you want your results formatted and what types of actions and reducers are considered generic.
Results are returned as an object bundle that follows the Ducks modular format which means related action types, action creators, selectors, and reducers are defined in the same file. In my experience, ducks is a great system. However, if it's not your thing, you can still use some of the lower-level tools in this library (although you might not avoid as much boilerplate code).
Actions are created by combining a "noun" and a "verb". In the case of "ADD_TODO"
, the verb would be "add"
and the noun would be "todo"
. Some string manipulation is applied so "Add 10 to", "totalScore"
would become the action type "ADD_10_TO_TOTAL_SCORE"
and the action creator would be called add10ToTotalScore()
.
A bundle created with boilerplate-reducer
returns an object with the following...
- Action type constants: e.g.
SET_SCORE
- Action creators: e.g.
setScore()
- An alias for the action creator without the noun name: e.g.
set()
- A selector that returns the value when given the store's state: e.g.
selectScore()
orselect()
- An object containing single-purpose reducers with the actions they respond to as keys called
reducers
- An automatically-combined reducer called
reducer()
The generateBundle()
function adds no actions by default but you can use the preset bundles to automatically create commonly used actions.
-
generateNumber()
- set - Sets the value to the payload value
- reset - Sets the value to the initial state value
- increment - Adds payload value to the number or adds 1 if there is no payload
- decrement - Subtracts payload value from the number or subtracts 1 if there is no payload
-
generateBoolean()
- set
- reset
- toggle - Sets true to false and vice versa
-
generateString()
- set
- reset
-
More to come... Please send your suggestions
(note: generateBundle()
has no preset verbs.)
With boilerplate-reducer
, this one-line module...
import {generateNumber} from 'boilerplate-reducer'
export default generateNumber('score', 0)
... is roughly equivalent to this...
const SET_SCORE = 'SET_SCORE'
const setScore = (newScore) => ({
type: SET_SCORE,
payload: newScore
})
const set = setScore
const RESET_SCORE = 'RESET_SCORE'
const resetScore = () => ({
type: RESET_SCORE
})
const reset = resetScore
const INCREMENT_SCORE = 'INCREMENT_SCORE'
const incrementScore = (additionalScore = 1) => ({
type: INCREMENT_SCORE,
payload: additionalScore
})
const increment = incrementScore
const DECREMENT_SCORE = 'DECREMENT_SCORE'
const decrementScore = (additionalScore = 1) => ({
type: DECREMENT_SCORE,
payload: additionalScore
})
const decrement = decrementScore
// Selector
const selectScore = state => state.score
const select = selectScore
// Initial State
const initialState = 0
// Reducer
const reducer = (previousScore = initialState, action) => {
switch (action.type) {
case RESET_SCORE:
return initialState
case SET_SCORE:
return action.payload
case INCREMENT_SCORE:
return previousScore + action.payload
case DECREMENT_SCORE:
return previousScore - action.payload
default:
return previousScore
}
}
export default {
name: 'score',
SET_SCORE,
setScore,
set,
RESET_SCORE,
resetScore,
reset,
INCREMENT_SCORE,
incrementScore,
increment,
DECREMENT_SCORE,
decrementScore,
decrement,
selectScore,
select,
reducers,
reducer
}
Add an action to double the score.
import {generateNumber} from 'boilerplate-reducer'
export default generateNumber('score', 0, {
'double': score => score * 2
})
Providing the object with a verb associated with a function creates an additional action-type, action-creator, and reducer inside score similar to...
{
DOUBLE_SCORE: "DOUBLE_SCORE",
doubleScore: () => {type: DOUBLE_SCORE},
reducer: (score, action) => {
if (action.type === "DOUBLE_SCORE") { return score * 2 }
}
}
Add an action to bump the score by 1,000 when a level is completed.
// Manually add the new reducer to the reducers object
score.reducers[level.COMPLETE_LEVEL] = score => score + 1000
// Reducers are automatically merged
score.reduce(9000, {type: level.COMPLETE_LEVEL}) // 10000
By default, the generated selector returns the value with the same name in the state object. However, it's very possible that your state object will be nested or have a different name than your bundle name. To provide a custom selector, just replace the old one.
// Manually replace selector.
score.selectScore = (state) => state.player.currentScore
For more, check out the unit tests.
- Ducks modular format
- Redux best practices (Uh... according to whom!?)
- reducing boilerplate
- combining reducer logic