redux-modular
Helpers for scaling and abstracting redux by co-locating actions, reducers and selectors.
Install
$ npm install --save redux-modular
or
$ yarn add redux-modular
Usage
import { combineReducers, createStore } from 'redux'
import { mount, createReducer } from 'redux-modular'
const counter = {
actions: {
increment: null,
decrement: null,
set: (value) => ({ value })
},
reducer: actions => createReducer(0, {
[actions.increment]: state => state + 1,
[actions.decrement]: state => state - 1,
[actions.set]: (state, payload) => payload.value
}),
selectors: localStateSelector => ({
counterValue: state => localStateSelector(state)
})
}
const counter1 = mount('counter1', counter)
const counter2 = mount('counter2', counter)
const counter3 = mount(['nested', 'counter3'], counter)
const rootReducer = combineReducers({
counter1: counter1.reducer,
counter2: counter2.reducer,
nested: combineReducers({
counter3: counter3.reducer
})
})
const store = createStore(rootReducer)
const { actions, selectors } = counter1
console.log(selectors.counterValue(store.getState()))
store.dispatch(actions.increment())
console.log(selectors.counterValue(store.getState()))
store.dispatch(actions.decrement())
console.log(selectors.counterValue(store.getState()))
store.dispatch(actions.set(5))
console.log(selectors.counterValue(store.getState()))
Writing Tests
If you mount
your logic to a path of null
, you can test your state logic without any assumption of where it sits in your redux state.
const counter = require('./counter')
const { actions, reducer, selectors } = mount(null, counter)
it('can increment', () => {
const state = reducer(0, actions.increment())
expect(selectors.counterValue(state)).toEqual(1)
})
it('can decrement', () => {
const state = reducer(0, actions.decrement())
expect(selectors.counterValue(state)).toEqual(-1)
})
it('can be set to a number', () => {
const state = reducer(0, actions.set(5))
expect(selectors.counterValue(state)).toEqual(5)
})