Illarion Koperski
2017-03-08
Using immutable data structures allows me to:
or Redux Reducer Bundles
A proposal by @erikras to keep reducer/actions pairs in an isolated module
Duck bill dog muzzle image by Oppo
_browserify
├── redux/
│ ├── middleware/
│ ├── modules/
│ │ ├── metafields.js
│ │ └── reducer.js
│ ├── types/
│ │ └── metafields.js
│ └── create.js
└── index.js
import uuid from 'uuid/v1'
import Immutable from 'immutable'
import assign from 'lodash/assign'
// Actions
import { ADD, REMOVE } from '../types/metafields'
// Reducer
const initialState = Immutable.Map({})
export default function reducer (state = initialState, action = {}) {
if (action.type === ADD) {
return state.set(action.payload.id, Immutable.Map(action.payload))
}
if (action.type === REMOVE) {
return state.removeIn([action.payload.id])
}
return state
}
// Action creators
export function add (payload = {}) {
const defaults = { id: uuid(), key: '', title: '' }
return { type: ADD, payload: assign(defaults, payload) }
}
export function remove (payload = {}) {
return { type: REMOVE, payload: assign({ id: '' }, payload) }
}
// Calculated view state
export function getViewState (state) { return state }
export const ADD = 'app/metafields/ADD'
export const REMOVE = 'app/metafields/REMOVE'
import test from 'tape'
import Immutable from 'immutable'
import reducer from '../../_browserify/redux/modules/metafields'
import { ADD, REMOVE } from '../../_browserify/redux/types/metafields'
const actions = {
add: {
type: ADD,
payload: { id: 'UID', key: 'test_field', title: 'Test field' }
},
remove: { type: REMOVE, payload: { id: 'UID' } }
}
test('Metafields reducer', (t) => {
t.assert(Immutable.is(reducer(undefined, {}), Immutable.Map({})),
'should return the initial state')
t.deepEqual(
reducer(undefined, actions.add).toJS(),
{ 'UID': actions.add.payload },
'should handle adding a metafield'
)
const actual = (() => {
const initialState = Immutable.fromJS({ 'UID': actions.add.payload })
return reducer(initialState, actions.remove).toJS()
})()
t.deepEqual(actual, {}, 'should handle adding a metafield')
t.end()
})
import test from 'tape'
import isUUID from 'validator/lib/isUUID'
import get from 'lodash/get'
import { add, remove } from '../../_browserify/redux/modules/metafields'
import { ADD, REMOVE } from '../../_browserify/redux/types/metafields'
const expected = {
add: {
type: ADD,
payload: { id: 'UID', key: 'test_field', title: 'Test field' }
},
remove: { type: REMOVE, payload: { id: 'UID' } }
}
test('Metafields actions', (t) => {
t.deepEqual(add(expected.add.payload), expected.add,
'should add a metafield')
t.ok(isUUID(
get(add({ key: 'test_field', title: 'Test field' }), 'payload.id')),
'should generate UUID for id field')
t.deepEqual(remove(expected.remove.payload), expected.remove,
'should remove a metafield')
t.end()
})
import { createStore, applyMiddleware } from 'redux'
import Immutable from 'immutable'
import { composeWithDevTools } from 'redux-devtools-extension/logOnly'
import reducer from './modules/reducer'
const composeEnhancers = composeWithDevTools({
serialize: {
immutable: Immutable
}
})
const initialState = Immutable.Map()
export default createStore(reducer, initialState, composeEnhancers(
// applyMiddleware(...middleware),
// other store enhancers if any
))
import { combineReducers } from 'redux-immutable'
import metafields from './metafields'
export default combineReducers({
metafields: metafields
// other store reducers
})
Epic is a function which takes a stream of actions and returns a stream of actions. Actions in, actions out.
Redux
RxJS
Epics
+
=