Skip to content

Modular and Extensible Redux Reducer Bundles (ducks-modular-redux)

License

Notifications You must be signed in to change notification settings

aga5tya/extensible-duck

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

24 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

extensible-duck

extensible-duck is an implementation of the Ducks proposal. With this library you can create reusable and extensible ducks.

Travis build status Code Climate Test Coverage Dependency Status devDependency Status

Basic Usage

// widgetsDuck.js

import Duck from 'extensible-duck'

export new Duck({
  namespace: 'my-app', store: 'widgets',
  types: ['LOAD', 'CREATE', 'UPDATE', 'REMOVE'],
  initialState: {},
  reducer: (state, action, duck) => {
    switch(action.type) {
      // do reducer stuff
      default: return state
    }
  },
  selectors: {
    root: state => state
  },
  creators: (duck) => ({
    loadWidgets:      () => ({ type: duck.types.LOAD }),
    createWidget: widget => ({ type: duck.types.CREATE, widget })
    updateWidget: widget => ({ type: duck.types.UPDATE, widget })
    removeWidget: widget => ({ type: duck.types.REMOVE, widget })
  })
})
// reducers.js

import { combineReducers } from 'redux'
import widgetDuck from './widgetDuck'

export default combineReducers({ widgets: widgetDuck.reducer })

Constructor Arguments

const { namespace, store, types, consts, initialState, creators } = options

Name Description Type Example
namespace Used as a prefix for the types String 'my-app'
store Used as a prefix for the types String 'widgets'
types List of action types Array [ 'CREATE', 'UPDATE' ]
consts Constants you may need to declare Object of Arrays { statuses: [ 'LOADING', 'LOADED' ] }
initialState State passed to the reducer when the state is undefined Anything {}
reducer Action reducer function(state, action, duck) (state, action, duck) => { return state }
creators Action creators function(duck) duck => ({ type: types.CREATE })
selectors state selectors Object of functions { root: state => state}

Duck Accessors

  • duck.reducer
  • duck.creators
  • duck.selectors
  • duck.types
  • for each const, duck.<const>

Creating Reusable Ducks

This example uses redux-promise-middleware and axios.

// remoteObjDuck.js

import Duck from 'extensible-duck'
import axios from 'axios'

export default function createDuck({ namespace, store, path, initialState={} }) {
  return new Duck({
    namespace, store,

    consts: { statuses: [ 'NEW', 'LOADING', 'READY', 'SAVING', 'SAVED' ] },

    types: [
      'UPDATE',
      'FETCH', 'FETCH_PENDING',  'FETCH_FULFILLED',
      'POST',  'POST_PENDING',   'POST_FULFILLED',
    ],

    reducer: (state, action, { types, statuses, initialState }) => {
      switch(action.type) {
        case types.UPDATE:
          return { ...state, obj: { ...state.obj, ...action.payload } }
        case types.FETCH_PENDING:
          return { ...state, status: statuses.LOADING }
        case types.FETCH_FULFILLED:
          return { ...state, obj: action.payload.data, status: statuses.READY }
        case types.POST_PENDING:
        case types.PATCH_PENDING:
          return { ...state, status: statuses.SAVING }
        case types.POST_FULFILLED:
        case types.PATCH_FULFILLED:
          return { ...state, status: statuses.SAVED }
        default:
          return state
      }
    },

    creators: ({ types }) => ({
      update: (fields) => ({ type: types.UPDATE, payload: fields }),
      get:        (id) => ({ type: types.FETCH, payload: axios.get(`${path}/${id}`),
      post:         () => ({ type: types.POST, payload: axios.post(path, obj) }),
      patch:        () => ({ type: types.PATCH, payload: axios.patch(`${path}/${id}`, obj) })
    }),

    initialState: ({ statuses }) => ({ obj: initialState || {}, status: statuses.NEW, entities: [] })
  })
}
// usersDuck.js

import createDuck from './remoteObjDuck'

export default createDuck({ namespace: 'my-app', store: 'user', path: '/users' })
// reducers.js

import { combineReducers } from 'redux'
import userDuck from './userDuck'

export default combineReducers({ user: userDuck.reducer })

Extending Ducks

This example is based on the previous one.

// usersDuck.js

import createDuck from './remoteObjDuck.js'

export default createDuck({ namespace: 'my-app',store: 'user', path: '/users' }).extend({
  types: [ 'RESET' ],
  reducer: (state, action, { types, statuses, initialState }) => {
    switch(action.type) {
      case types.RESET:
        return { ...initialState, obj: { ...initialState.obj, ...action.payload } }
      default:
        return state
  },
  creators: ({ types }) => ({
    reset: (fields) => ({ type: types.RESET, payload: fields }),
  })
})

Creating Reusable Duck Extensions

This example is a refactor of the previous one.

// resetDuckExtension.js

export default {
  types: [ 'RESET' ],
  reducer: (state, action, { types, statuses, initialState }) => {
    switch(action.type) {
      case types.RESET:
        return { ...initialState, obj: { ...initialState.obj, ...action.payload } }
      default:
        return state
  },
  creators: ({ types }) => ({
    reset: (fields) => ({ type: types.RESET, payload: fields }),
  })
}
// userDuck.js

import createDuck from './remoteObjDuck'
import reset from './resetDuckExtension'

export default createDuck({ namespace: 'my-app',store: 'user', path: '/users' }).extend(reset)

Creating Ducks with selectors

Selectors help in providing performance optimisations when used with libraries such as React-Redux, Preact-Redux etc.

// Duck.js

import Duck from 'extensible-duck'

export new Duck({
  initialState: {
    items: [
      { name: 'apple', value: 1.2 },
      { name: 'orange', value: 0.95 }
    ]
  },
  reducer: (state, action, duck) => {
    switch(action.type) {
      // do reducer stuff
      default: return state
    }
  },
  selectors: {
    items: state => state.items, // gets the items from state
    subTotal: selectors => state =>
      // Get another derived state reusing previous selector. In this case items selector
      // Can compose multiple such selectors if using library like reselect. Recommended!
      // Note: The order of the selectors definitions matters
      selectors
        .items(state)
        .reduce((computedTotal, item) => computedTotal + item.value, 0)
  }
})
// HomeView.js
import React from 'react'
import Duck from './Duck'

@connect(state => ({
  items: Duck.selectors.items(state),
  subTotal: Duck.selectors.subTotal(state)
}))
export default class HomeView extends React.Component {
  render(){
    // make use of sliced state here in props
    ...
  }
}

About

Modular and Extensible Redux Reducer Bundles (ducks-modular-redux)

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages

  • JavaScript 95.6%
  • HTML 4.4%