Make Redux reducers with less effort.
http://reducify.mediadrake.com/
% npm install reducify
import { createStore } from 'redux';
import reducify from 'reducify';
const store = createStore(
reducify({
"ADD": (state = 0, action) => state + action.data,
"SUBTRACT": (state = 0, action) => state - action.data
})
);
store.dispatch({
type: 'ADD',
data: 10
});
// State is: 10
store.dispatch({
type: 'SUBTRACT',
data: 5
});
// State is: 5
Tired of massive, unwieldly switch statements? Wish you could break up reducers into re-usable and configurable parts?
Yes, this problem is literally ruining my life.
We thought so. With Reducify you can create reducers with a configurations and sleep a bit easier.
The vanilla approach. Passing in a function just spits out the same function.
If you've got your reducers all squared away we don't want to rock the boat.
function myReducer(state, action) {
switch(action.type) {
// reducer stuff
}
}
createStore(reducify(myReducer));
If you pass in a config, we'll turn it into a reducer function.
Check out the config api reference to see what you can add.
const myConfig = {
defaultsTo: 10
reducer(state, action) { // state will be 10
// reducer stuff
}
};
createStore(reducify(myConfig));
Passing in an array is just a short version of the config above.
const myArrayConfig = [
10,
(state, action) => { // state will be 10
// reducer stuff
}
];
createStore(reducify(myArrayConfig));
Arrays are deconstructed with the following signature:
[defaultsTo, [select, merge], reducerAndActions]
Some examples:
// No Select
[10, myReducerFunction]
// Same as:
{defaultsTo: 10, reducer: myReducerFunction}
// Key Select
[{myCount: 10}, 'myCount', myReducerFunction]
// Same as:
{defaultsTo: {myCount: 10}, select: 'myCount', reducer: myReducerFunction}
// Long form select
[
{myCount: 10},
state => state.myCount,
(result, state) => {...state, myCount: result},
myReducerFunction
]
// Same as:
{
defaultsTo: {myCount: 10},
select: state => state.myCount,
merge: (result, state) => {...state, myCount: result},
reducer: myReducerFunction
}
Because we're opening the door on configuration, we get the ability to add in some user-directed magic that solves common redux boilerplate.
This might bring out some pitchforks, but you don't need to do a switch statement for everything. If you pass in an action type that is a method, we'll run them before we run any declared reducers.
import { createStore } from 'redux';
import reducify from 'reducify';
const store = createStore(
reducify({
"INCREMENT": (state = 0, action) => state + 1,
"DECRAMENT": (state = 0, action) => state - 1
})
);
store.dispatch({
type: 'INCREMENT'
});
// State is 1
store.dispatch({
type: 'DECREMENT'
});
// State is 0
The following is the same as above
import { createStore } from 'redux';
import reducify from 'reducify';
const store = createStore(
function(state = 0, action) {
switch(action.type) {
case "INCREMENT":
return state + 1;
case "DECREMENT":
return state - 1;
default:
return state;
}
}
);
// same as above
Nice! We went from 10 lines to 4. Not bad.
Keep in mind, this is still Redux. So don't take any shortcuts like trying to not make copies of your objects.
// GOOD
const store = createStore(
reducify({
"ADD_PITCHFORK": (state = {pitchforks: 0}, action) => ({...state, pitchforks: state.pitchforks + 1}),
"USE_PITCHFORK": (state = {pitchforks: 0}, action) => ({...state, pitchforks: state.pitchforks - 1})
})
);
// BAD
const store = createStore(
reducify({
"ADD_PITCHFORK": (state = {pitchforks: 0}, action) => {
state.pitchforks ++;
return state;
},
"USE_PITCHFORK": (state = {pitchforks: 0}, action) => {
state.pitchforks --;
return state;
}
})
);
You can even combine these methods with a reducer function! The actions will always run first.
import { createStore } from 'redux';
import reducify from 'reducify';
const store = createStore(
reducify({
"ADD_PITCHFORK": (state = {pitchforks: 0}, action) => ({...state, pitchforks: state.pitchforks + 1}),
"USE_PITCHFORK": (state = {pitchforks: 0}, action) => ({...state, pitchforks: state.pitchforks - 1}),
reducer(state, action) {
switch(action.type) {
case "CLEAR_PITCHFORKS":
return {...state, pitchforks: 0};
default:
return state;
}
}
})
);
get it, you won't chase us down with pitchforks because we're letting you use switch statements too? Nevermind, sigh - moving on
Ever dealt with mutating a large redux object? It's not a lot of fun to try and peak at your model over a massive switch statement and just hope you're getting it right.
It's even less fun to have to deal with updating your model because Brad in product design thinks that we should go from having 1 user profile picture to 10.
You're a jerk Brad. I ate all of the spaghetti you brought in for lunch - it was only ok.
import { createStore } from 'redux';
import reducify from 'reducify';
const store = createStore(
reducify({
defaultsTo: {username: 'Brad', hasSpaghetti: true},
select: 'hasSpaghetti',
"EAT_SPAGHETTI": (state, action) => false
})
);
store.dispatch({
type: 'EAT_SPAGHETTI'
});
// State is: {username: 'Brad', hasSpaghetti: false}
In the example above, we passed a string to the select
method. The string is mapped to an object key that we automatically merge and select from.
You can pass in select and merge methods. This would be identical to the reducer above:
import { createStore } from 'redux';
import reducify from 'reducify';
const store = createStore(
reducify({
defaultsTo: {username: 'Brad', hasSpaghetti: true},
select: (state) => state.hasSpaghetti,
merge: (result, state) => ({...state, hasSpaghetti: result})
"EAT_SPAGHETTI": (state, action) => false
})
);
store.dispatch({
type: 'EAT_SPAGHETTI'
});
// State is: {username: 'Brad', hasSpaghetti: false}
And you can use some aliases - $
for select and _
for merge.
const store = createStore(
reducify({
defaultsTo: {username: 'Brad', hasSpaghetti: true},
$: 'hasSpaghetti'
"EAT_SPAGHETTI": (state, action) => false
})
);
// eating Brad's spaghetti
const store = createStore(
reducify({
defaultsTo: {username: 'Brad', hasSpaghetti: true},
$: (state) => state.hasSpaghetti,
_: (result, state) => ({...state, hasSpaghetti: result})
"EAT_SPAGHETTI": (state, action) => false
})
);
// still eating Brad's spaghetti
If you're trying to access an object that's nested into your state, you can pass in an array and we'll traverse that path for you
import { createStore } from 'redux';
import reducify from 'reducify';
const store = createStore(
reducify({
defaultsTo: {username: 'Brad', lunch: {hasSpaghetti: true}},
select: ['lunch', 'hasSpaghetti'],
"EAT_SPAGHETTI": (state, action) => false
})
);
store.dispatch({
type: 'EAT_SPAGHETTI'
});
// State is: {username: 'Brad', lunch: {hasSpaghetti: false}}
When you're declaring your reducer, you've got a chance to set some default values for all actions that go through it.
import { createStore } from 'redux';
import reducify from 'reducify';
function incrementReducer(state = 0, {data = 1, ...action}) {
switch (action.type) {
case 'INCREMENT':
return state + data;
case 'DECREMENT':
return state - data;
default:
return state;
}
}
const store = createStore(
reducify({
reducer: incrementReducer,
actionPart: {data: 2}
})
);
store.dispatch({
type: 'INCREMENT'
});
// State is: 2
store.dispatch({
type: 'DECREMENT'
});
// State is: 0
Just use the config option defaultsTo
.
import { createStore } from 'redux';
import reducify from 'reducify';
const store = createStore(
reducify({
defaultsTo: {myNumber: 10},
select: 'myNumber',
"ADD": (state = 0, action) => state + action.data,
"SUBTRACT": (state = 0, action) => state - action.data
})
);
store.dispatch({
type: 'ADD',
data: 20
});
// State is: {myNumber: 30}
store.dispatch({
type: 'SUBTRACT',
data: 5
});
// State is: {myNumber: 25}
You will get a state with all of your reducers, so if you're relying on method signature defaults, that will get overridden.
const store = createStore(
reducify({
defaultsTo: {myNumber: 10},
select: 'myNumber',
"ADD": (state = 0, action) => state + action.data,
"SUBTRACT": (state = 0, action) => state - action.data
})
);
A cousin of defaultsTo
. Static reducers just return the state or default value regardless of action type.
import { createStore } from 'redux';
import reducify from 'reducify';
const store = createStore(
reducify({
foo: 'bar'
})
);
store.dispatch({
type: 'ADD',
data: 20
});
// State is: {foo: 'bar'}
store.dispatch({
type: 'SUBTRACT',
data: 5
});
// State is: {foo: 'bar'}
Pass in a plain object or value and that's what you'll get back every time. Good for mocking and some plugins.
Reducify is free software under the MIT license. It was created in sunny Santa Monica by Matthew Drake.