tl;dr: redux-create-module
Suppose you want to create a new module in your react-redux app.
You know the drill.
Create the action type constants, make and the action creators, and handle the types in the reducer.
You may have noticed that the process looks the same almost every time.
We, as developers, know that this kind of things could, and should be abstracted and automated.
So, first let's think what's the best solution for Developer Experience.
Or, since we are the Developers, let's just Rethink Redux.
Well, if we are only interested in mapping actions to states, my first thought is to make a Map. Or a regular object, for that matter.
const counterMap = {
increment: (state) => state + 1,
decrement: (state) => state -1
}
// How do we use it? Simple. With a reducer.
const reducer = (state = 0, action) =>
(counterMap[action.type])
? counterMap[action.type](state)
: state
But we don't want to create the reducer. We only want to think about the Map. Let's make a function that takes a Map and returns a reducer
const createReducer = (initialState, map) =>
(state = initialState, action) =>
(map[action.type])
? map[action.type](state)
: state
const reducer = createReducer(0, counterMap)
reducer(0, {type: "increment"}) // => 1
So simple! Or, too simple! Because we have a problem here. Action creators were invented for a reason, and that's because some actions need to have a payload to be handled... Or is it?
No! of course, there is another reason and that's because it's unsafe and ineffective to pass strings to the reducer. What if we make a typo on it?!
So let's get more serious now.
We still don't want to create the action creators manually, and, why should we?
Think about it, createReducer
already has all the information it needs to make them.
It can get the types of our actions from the keys
of the Map.
So, let's make it return both, a reducer and the action creators, and name it createModule
const createModule = (initialState, map) => {
const reducer = (state = initialState, action) =>
(map[action.type])
? map[action.type](state)
: state
const actions = Object.keys(map).reduce(
(acc, type) =>
Object.assign(acc, {
[type]: (payload = {}) => ({ type, payload })
}),
{}
);
}
// example
const counterMap = {
add: (state, {payload}) => state + payload
}
export const counter = createModule(0, counterMap)
// then somewhere in a connected component...
dispatch(counter.actions.add(2))
Neat!
Of course, we still have some todos. like namespacing our actions to avoid conflicts, and allowing a reducer to handle an action from another module.
But we won't get into that in this post.
Instead, I'll refer you to the source of the little module I made.
Thanks for reading!
Photo: Sunrise in the Sea by me :)
Top comments (0)