moducksdeprecated

Ducks (Redux Reducer Bundles) + Redux-Saga = Moducks

Usage no npm install needed!

<script type="module">
  import moducks from 'https://cdn.skypack.dev/moducks';
</script>

README

Moducks

moducks

npm version Build Status

Ducks (Redux Reducer Bundles) + Redux-Saga = Moducks

Getting Started

Installing

npm install moducks --save

Contents

Motivation

Please consider the following fake API:

import { delay } from 'redux-saga'

export const fetchRandomUser = async () => {

  await delay((0.3 + Math.random()) * 1000)

  if (Math.random() < 0.2) {
    // Sometimes it fails
    const faces = ['xD', ':D', ':(']
    throw new Error(`503 Service Unavailable ${faces[Math.floor(Math.random() * faces.length)]}`)
  }

  const users = [
    { name: 'John' },
    { name: 'Mary' },
    { name: 'Bob' },
    { name: 'Cathy' },
    { name: 'Mike' },
  ]

  return users[Math.floor(Math.random() * users.length)]
}

Without moducks, you have to define lengthy definitions for each module.

import { call, put, takeEvery } from 'redux-saga/effects'
import { fetchRandomUser } from '../api'

const LOAD = '@@myApp/randomUser/LOAD'
const LOAD_SUCCESS = '@@myApp/randomUser/LOAD_SUCCESS'
const LOAD_FAILURE = '@@myApp/randomUser/LOAD_FAILURE'
const CLEAR = '@@myApp/randomUser/CLEAR'

export const load = () => ({ type: LOAD })
const loadSuccess = data => ({ type: LOAD_SUCCESS, payload: data })
const loadFailure = error => ({ type: LOAD_FAILURE, payload: error, error: true })
export const clear = () => ({ type: CLEAR })

const initialState = {
  users: [],
  errors: [],
  pendingCounts: 0,
}

export default (state = initialState, { type, payload }) => {
  switch (type) {

    case LOAD:
      return {
        ...state,
        pendingCounts: state.pendingCounts + 1,
      }

    case LOAD_SUCCESS:
      return {
        ...state,
        users: [ ...state.users, payload.name ],
        pendingCounts: state.pendingCounts - 1,
      }

    case LOAD_FAILURE:
      return {
        ...state,
        errors: [ ...state.errors, payload.message ],
        pendingCounts: state.pendingCounts - 1,
      }

    case CLEAR:
      return {
        ...state,
        users: [],
        errors: [],
      }

    default:
      return state
  }
}

export const sagas = {

  load: takeEvery(LOAD, function* (action) {
    try {
      yield put(loadSuccess(yield call(fetchRandomUser)))
    } catch (e) {
      yield put(loadFailure(e))
    }
  }),

}

With moducks, module definition will be extremely simple. The following snippet is equivalent to the above.

import Moducks from 'moducks'
import * as effects from 'redux-saga/effects'
import { fetchRandomUser } from '../api'

const moducks = new Moducks({ effects, appName: 'myApp' })

const initialState = {
  users: [],
  errors: [],
  pendingCounts: 0,
}

const {
  randomUser, sagas,
  load, loadSuccess, loadFailure, clear,
} = moducks.createModule('randomUser', {

  LOAD: {
    reducer: state => ({
      ...state,
      pendingCounts: state.pendingCounts + 1,
    }),
    saga: function* (action) {
      return loadSuccess(yield effects.call(fetchRandomUser))
    },
    onError: (e, action) => loadFailure(e),
  },

  LOAD_SUCCESS: (state, { payload: user }) => ({
    ...state,
    users: [ ...state.users, user.name ],
    pendingCounts: state.pendingCounts - 1,
  }),

  LOAD_FAILURE: (state, { payload: e }) => ({
    ...state,
    errors: [ ...state.errors, e.message ],
    pendingCounts: state.pendingCounts - 1,
  }),

  CLEAR: state => ({
    ...state,
    users: [],
    errors: [],
  }),

}, initialState)

export default randomUser
export { sagas, load, clear }