deduce v2.0.0-2
deduce
Ridiculously easy JavaScript state containers with reducer methods. Like Redux without all of the boilerplate.
Install
npm install --save deduce
Usage
// reducers.js
export function increment(state, val = 1) {
return state + val;
}
export function decrement(state, val = 1) {
return increment(state, -val);
}
// store.js
import deduce from 'deduce';
import * as reducers from './reducers';
const store = deduce(1, reducers);
store.addListener(() => {
console.log(store.state);
});
store.increment(); // -> 2
store.increment(2); // -> 4
store.decrement(); // -> 3
store.decrement(2); // -> 1
API
deduce(initialState, reducers) : Store
initialState
{*}
reducers
{Object<String,Function>}
Store
.state
Current state of the store.
const store = deduce({ foo: 1 });
console.log(store.state); // -> { foo: 1 }
.addReducers(reducers): Store
reducers
{Object<String,Function>}
Registers reducers to modify the state. Chainable.
store.addReducers({
increment(state, val) {
return {
...state,
foo: state.foo + val,
};
},
});
.addReducersFor(property, reducers): Store
property
{String}
reducers
{Object<String,Function>}
Registers reducers to modify a specific state property. Chainable.
store.addReducersFor('foo', {
increment(state, val) {
return state + val;
},
});
.addListener(callback): Function
callback
{Function}
Adds a listener to be called any time the state is updated. Returns a function to remove the listener.
const removeListener = store.addListener(() => {
console.log(store.state);
});
store.increment();
Why?
The typical Redux patterns entail a lot of boilerplate. The documented and accepted patterns for reducing boilerplate really just swap one kind for another:
Redux Example
Consider the following Redux example that creates a store with two numbers: foo
which may be incremented and bar
which may be decremented.
// foo
const FOO_INCREMENT = 'FOO_INCREMENT';
const fooInitial = 0;
const fooReducers = {
[FOO_INCREMENT]: (state = fooInitial, action) {
return state + action.payload;
}
};
function foo(state = {}, action) {
if (action.type in fooReducers) {
return fooReducers[action.type](state, action);
}
return state;
}
function createFooIncrementAction(payload) {
return {
type: FOO_INCREMENT,
payload
};
}
// bar
const BAR_DECREMENT = 'BAR_DECREMENT';
const barInitial = 0;
const barReducers = {
[BAR_DECREMENT]: (state = barInitial, action) {
return state - action.payload;
}
};
function bar(state, action) {
if (action.type in barReducers) {
return barReducers[action.type](state, action);
}
return state;
}
function createBarDecrementAction(payload) {
return {
type: BAR_DECREMENT,
payload
};
}
// store
import { createStore, combineReducers } from 'redux';
const reducer = combineReducers({ foo, bar });
const store = createStore(reducer, {});
// application
store.dispatch(createFooIncrementAction(1));
store.dispatch(createBarDecrementAction(1));
console.log(store.getState());
// {
// foo: 1,
// bar: -1
// }
Split that up into modules and you can see how new-comers could easily be overwhelmed when the underlying principles are beautifully clean and simple.
Deduce Example
Compare the above with this deduce
example that does the same thing:
// foo
const fooInitial = 0;
const fooReducers = {
incrementFoo(state = fooInitial, val) {
return state + val;
}
};
// bar
const barInitial = 0;
const barReducers = {
decrementBar(state = barInitial, val) {
return state - val;
}
};
// store
import deduce from 'deduce';
const store = deduce()
.addReducersFor('foo', fooReducers)
.addReducersFor('bar', barReducers);
// application
store.incrementFoo(1);
store.decrementBar(1);
console.log(store.state);
// {
// foo: 1,
// bar: -1
// }
Contribute
Standards for this project, including tests, code coverage, and semantics are enforced with a build tool. Pull requests must include passing tests with 100% code coverage and no linting errors.
Test
$ npm test
MIT © Shannon Moeller