Skip to content

Scaling redux by co-locating actions, reducers and selectors.

Notifications You must be signed in to change notification settings

thomasdashney/redux-modular

Repository files navigation

redux-modular

Helpers for scaling and abstracting redux by co-locating actions, reducers and selectors.

Build Status Test Coverage Code Climate

Install

$ npm install --save redux-modular

or

$ yarn add redux-modular

Usage

import { combineReducers, createStore } from 'redux'
import { mount, createReducer } from 'redux-modular'

/* Create an object containing the logic (actions, reducer, selectors) */

const counter = {
  // mapping of action names to optional payload creators
  actions: {
    increment: null,
    decrement: null,
    set: (value) => ({ value })
  },

  // function mapping actions to reducers
  reducer: actions => createReducer(0, {
    [actions.increment]: state => state + 1,
    [actions.decrement]: state => state - 1,
    [actions.set]: (state, payload) => payload.value
  }),

  // function mapping local state selector to your selectors
  selectors: localStateSelector => ({
    counterValue: state => localStateSelector(state)
  })
}

/* Instantiate the counter logic by mounting to redux paths */

const counter1 = mount('counter1', counter)
const counter2 = mount('counter2', counter)
const counter3 = mount(['nested', 'counter3'], counter)

/* Add the reducers to your root reducer */

const rootReducer = combineReducers({
  counter1: counter1.reducer,
  counter2: counter2.reducer,
  nested: combineReducers({
    counter3: counter3.reducer
  })
})

const store = createStore(rootReducer)

/* Use actions and selectors for each counter instance in your app */

const { actions, selectors } = counter1

console.log(selectors.counterValue(store.getState())) // prints `0`

store.dispatch(actions.increment())
console.log(selectors.counterValue(store.getState())) // prints `1`

store.dispatch(actions.decrement())
console.log(selectors.counterValue(store.getState())) // prints `0`

store.dispatch(actions.set(5))
console.log(selectors.counterValue(store.getState())) // prints `5`

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.

/* eslint-env jest */

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)
})

About

Scaling redux by co-locating actions, reducers and selectors.

Resources

Stars

Watchers

Forks

Packages

No packages published