Reducify
Make Redux reducers with less effort.
http://reducify.mediadrake.com/
Installation
% npm install reducify
Usage
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
});
store.dispatch({
type: 'SUBTRACT',
data: 5
});
Turn configurations into reducer functions
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.
Reducers Made Three Ways
Functions
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) {
}
}
createStore(reducify(myReducer));
Configs
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) {
}
};
createStore(reducify(myConfig));
Arrays
Passing in an array is just a short version of the config above.
const myArrayConfig = [
10,
(state, action) => {
}
];
createStore(reducify(myArrayConfig));
Arrays are deconstructed with the following signature:
[defaultsTo, [select, merge], reducerAndActions]
Some examples:
[10, myReducerFunction]
{defaultsTo: 10, reducer: myReducerFunction}
[{myCount: 10}, 'myCount', myReducerFunction]
{defaultsTo: {myCount: 10}, select: 'myCount', reducer: myReducerFunction}
[
{myCount: 10},
state => state.myCount,
(result, state) => {...state, myCount: result},
myReducerFunction
]
{
defaultsTo: {myCount: 10},
select: state => state.myCount,
merge: (result, state) => {...state, myCount: result},
reducer: myReducerFunction
}
Configuration Sugar
Because we're opening the door on configuration, we get the ability to add in some user-directed magic that solves common redux boilerplate.
Action Methods
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'
});
store.dispatch({
type: 'DECREMENT'
});
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;
}
}
);
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.
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})
})
);
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
Selectors
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.
String selectors
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'
});
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.
Selector Methods
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'
});
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
})
);
const store = createStore(
reducify({
defaultsTo: {username: 'Brad', hasSpaghetti: true},
$: (state) => state.hasSpaghetti,
_: (result, state) => ({...state, hasSpaghetti: result})
"EAT_SPAGHETTI": (state, action) => false
})
);
Deep selectors
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'
});
Action Partials
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'
});
store.dispatch({
type: 'DECREMENT'
});
Defaults
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
});
store.dispatch({
type: 'SUBTRACT',
data: 5
});
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
})
);
Statics
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
});
store.dispatch({
type: 'SUBTRACT',
data: 5
});
Pass in a plain object or value and that's what you'll get back every time. Good for mocking and some plugins.
Credits
Reducify is free software under the MIT license. It was created in sunny Santa Monica by Matthew Drake.