Repository: reduxjs/redux

Issue #1024: Proposal: declarative reducers
Created: 2015-11-12T12:19:00Z
I propose provide option for defining reducer with a plain JS object where keys are action types and functions defined with ES6 arrow functions (which leads to less code):
Here code example based on of Redux TodoMVC reducer.
How it declared now:
const initialState = [
  {
    text: 'Use Redux',
    completed: false,
    id: 0
  }
]

export default function todos(state = initialState, action) {
  switch (action.type) {
    case ADD_TODO:
      return [
        {
          id: state.reduce((maxId, todo) => Math.max(todo.id, maxId), -1) + 1,
          completed: false,
          text: action.text
        }, 
        ...state
      ]

    case DELETE_TODO:
      return state.filter(todo =>
        todo.id !== action.id
      )

    case EDIT_TODO:
      return state.map(todo =>
        todo.id === action.id ?
          Object.assign({}, todo, { text: action.text }) :
          todo
      )

    case COMPLETE_TODO:
      return state.map(todo =>
        todo.id === action.id ?
          Object.assign({}, todo, { completed: !todo.completed }) :
          todo
      )

    case COMPLETE_ALL:
      const areAllMarked = state.every(todo => todo.completed)
      return state.map(todo => Object.assign({}, todo, {
        completed: !areAllMarked
      }))

    case CLEAR_COMPLETED:
      return state.filter(todo => todo.completed === false)

    default:
      return state
  }
}
How I propose to declare:
export default {
  initialState: [
    {
      text: 'Use Redux',
      completed: false,
      id: 0
    }
  ],

  [ADD_TODO]: (state, {text}) => [
    {
      id: state.reduce((maxId, todo) => Math.max(todo.id, maxId), -1) + 1,
      completed: false,
      text
    }, 
    ...state
  ],

  [DELETE_TODO]: (state, {id}) => state.filter(todo => todo.id !== id),


  [EDIT_TODO]: (state, {id, text}) => 
    state.map(todo => (todo.id === id) ? {...todo, text} : todo),

  [COMPLETE_TODO]: (state, {id}) => 
    state.map(todo => (todo.id === id) ? { ...todo, completed: !todo.completed } : todo),

  [COMPLETE_ALL]: state => {
      const areAllMarked = state.every(todo => todo.completed);
      return state.map(todo => ({ ...todo, completed: !areAllMarked }))
  },

  [CLEAR_COMPLETED]: state => state.filter(todo => todo.completed === false)
}
Pros

Parameter destructuring e.g:

(state, {id, text}) => 
    state.map(todo => (todo.id === id) ? {...todo, text} : todo)

No "return" statement
No "case" statement
Less code
Better readability

And here function to convert declarative reducer to standard reducer:
function createReducer(reducerConfig){
  const { initialState, ...reducers } = reducerConfig;

  return (state = initialState, action) => {
    const reducer = reducers[action.type];

    if (typeof reducer === 'function'){
      return reducer(state, action);
    } else {
      return state;
    }
  };
}

Duplicate issue #1062: About combineReducers
Created: 2015-11-21T17:53:46Z
Hi, I want to share a conceptual problem I have with combineReducers in order to have the community opinion.
Example app
I have 3 actions : INCREMENT (step), DECREMENT (step), FOO;
my store is {
  count: a counter (will do something on INCREMENT, DECREMENT),
  color: will change only on INCREMENT
}

With combineReducers
I will have 2 reducers, I only show the count one below.
function count(state, action /* P3 */) {
  const step = action.step || 1;

  switch (action.type) { /* P1 */
  case 'INCREMENT':
    return state + step;

  case 'DECREMENT':
    return state - step;

  default: /* P2 */
    return state;
  }
}
P1 : the switch on action.type
This is my first problem. It sounds like an "instanceof" in OOP world very rarely used because it generally reveals that the model has an architectural problem solvable with polymorphism concept.
I think the problem is because we want here to mix functional programmation (the reducer) with Object oriented (the action payload represents an object with a type).
A solution : divide & conquer. If we split the reducer with a constraint 1 handler = 1 action
function count(state, action /* P3 */) {
  const step = action.step || 1;

  const map = {
    'INCREMENT': (state) => state + step,
    'DECREMENT': (state) => state - step
  }

  if (map[action.type] !== undefined) return map[action.type](state);

  return state;
}
Note : the partial solution is just here to understand the thought.
P2 : the "default" boilerplate
we must define the default case with a simple identity return. But why ? the lib may handle this factorization for us.
This problem is a direct consequence of the combineReducers broadcasting. Indeed combineReducers act as a broadcaster but I think a router here may be a better choice. With a router I will not have the default or the if (map[action.type] !== undefined) anymore.
The performance also is concerned but .. I know it's clearly not the breaking point of a real app so I don't hold this argument.
P3 : the signature
It's very difficult to maintain a big app without an explicit signature. Here we have an "action" but we don't know what is composed of and it is impossible because it can be all the actions (a growing set during the lifecycle of a development of an app).
"atomic" reducers
To solve all these conceptual problems I want to introduce atomic reducer : a reducer handling only one action type.
/**
 * @param {Object} action
 * @param {int} action.step 1 if undefined
 */
function countIncrementer(state, action) {
  return state + (action.step || 1);
}
Ok but ..

I don't like dockblock. I mean dockblock are generally here because the name of the function is not sufficient to understand its behaviour or because the signature is imprecise. In an untyped language like Javascript we are in this case, what "action" payload means ?
Divide responsibilities. My countIncrementer has to know how the count state structure is (as a count reducer), its behaviour, but the action structure .. not really, the action structure can evolve regardless of the count store.

function countIncrementer(state, step = 1) {
  return state + step;
}
Better :).
I also need a map to call the good reducer on an action. It will also have the responsibility to decrypt the action payload
// For example my partial store count.js file will export this descriptor
const count = {
  '@@redux/INIT': (state = 0) => state, // we can find a best syntax but this is the idea of default value or hydrated from createStore
  'INCREMENT': (state, action) => countIncrementer(state, action.step),
  'DECREMENT': (state, action) => countDecrementer(state, action.step)
};
And finally my main store
const store = createStore(combineAtomicReducers({
  color,
  count
}));
See complete example https://gist.github.com/jnoleau/8c30f8f4f1e70ea18c7d
Conclusion
I understand combineReducers is just an example of a rootReducer but in fact as it's included in the library I think a lot of people use it as a best practice. Maybe a solution could be to extract the combineReducers in another npm package ?
I would really appreciate your opinion about "atomic" router approach.
Thx.

Closing comment (2015-11-23T00:34:30Z): Thanks for the write-up! I'm closing as a duplicate of #1024, #883. Please see the discussion there.
From http://redux.js.org/docs/recipes/ReducingBoilerplate.html#generating-reducers:

Let’s write a function that lets us express reducers as an object mapping from action types to handlers. For example, if we want our todos reducers to be defined like this:
export const todos = createReducer([], {
  [ActionTypes.ADD_TODO](state, action) {
    let text = action.text.trim()
    return [ ...state, text ]
  }
})
We can write the following helper to accomplish this:
function createReducer(initialState, handlers) {
  return function reducer(state = initialState, action) {
    if (handlers.hasOwnProperty(action.type)) {
      return handlers[action.type](state, action)
    } else {
      return state
    }
  }
}
This wasn’t difficult, was it? Redux doesn’t provide such a helper function by default because there are many ways to write it. Maybe you want it to automatically convert plain JS objects to Immutable objects to hydrate the server state. Maybe you want to merge the returned state with the current state. There may be different approaches to a “catch all” handler. All of this depends on the conventions you choose for your team on a specific project.
The Redux reducer API is (state, action) => state, but how you create those reducers is up to you.