Reducer without switch #1167

Closed
mapreal19 opened this Issue Dec 22, 2015 · 5 comments

Comments

4 participants
@mapreal19

As reading from the docs you could avoid using switch with a function that maps action types to handlers. But still I'm not fully convinced with that approach.

In order to avoid those long switch statements, I suggest injecting the dependency of the reducers right into the actions.

The actions now will know how to change the state per each reducer. The reducer will just call them accordingly. This way, the reducer would follow the Open/Closed principle (Motivation by Uncle Bob: https://youtu.be/TMuno5RZNeE?t=3605)

I found this architecture much better. Take the case your system requires a new feature where 3 reducers listen to the same action. Then you would need to modify those reducers -- adding a new case on each switch statement.

Following the style I propose, you would only need to create the action with the reducers all in the same place. Example:

// ACTION
function addTodo() {
  reducers: {
    counterReducer: (state) => ...,
    todosReducer: (state) => ...,
    dummyReducer: (state) => ...
  }
}

// todos REDUCER
function todos(state = initialState, action) {
  if (action.reducers && typeof action.reducers.todosReducer === 'function') {
    return action.reducers.todosReducer(state);
  } else {
    return state;      
  }
}

Here you could see a refactor of the todomvc example using this idea.

Thoughts?

@evgenyrodionov

This comment has been minimized.

Show comment
Hide comment
@gaearon

This comment has been minimized.

Show comment
Hide comment
@gaearon

gaearon Dec 22, 2015

Collaborator

The whole point of Flux/Redux is to decouple actions and reducers. There may be many independent reducers handling one action, and there may be many independent actions handled by one reducer. This is what makes Flux/Redux scale because big teams can work on overlapping features without constant merge conflicts, as state mutation logic is kept separate even if it is caused by the same actions.

You also lose the ability to serialize and record/replay actions because they are not plain objects any more in your example. Enabling this was another big constraint of Redux. Please read this:

There are frameworks claiming to be similar to Flux, but without a concept of action objects. In terms of being predictable, this is a step backwards from Flux or Redux. If there are no serializable plain object actions, it is impossible to record and replay user sessions, or to implement hot reloading with time travel. If you’d rather modify data directly, you don’t need Redux.

Collaborator

gaearon commented Dec 22, 2015

The whole point of Flux/Redux is to decouple actions and reducers. There may be many independent reducers handling one action, and there may be many independent actions handled by one reducer. This is what makes Flux/Redux scale because big teams can work on overlapping features without constant merge conflicts, as state mutation logic is kept separate even if it is caused by the same actions.

You also lose the ability to serialize and record/replay actions because they are not plain objects any more in your example. Enabling this was another big constraint of Redux. Please read this:

There are frameworks claiming to be similar to Flux, but without a concept of action objects. In terms of being predictable, this is a step backwards from Flux or Redux. If there are no serializable plain object actions, it is impossible to record and replay user sessions, or to implement hot reloading with time travel. If you’d rather modify data directly, you don’t need Redux.

@gaearon gaearon closed this Dec 22, 2015

@gaearon

This comment has been minimized.

Show comment
Hide comment
@gaearon

gaearon Dec 22, 2015

Collaborator

Think of action as a "message". The action doesn't know how the state changes. It's precisely reducers' job. Otherwise your reducers don't seem to contain code at all. Also don't forget that reducers can be composed further than a single level. What you propose doesn't work with reducer composition because you're effectively hardcoding reducer structure into the action objects.

Collaborator

gaearon commented Dec 22, 2015

Think of action as a "message". The action doesn't know how the state changes. It's precisely reducers' job. Otherwise your reducers don't seem to contain code at all. Also don't forget that reducers can be composed further than a single level. What you propose doesn't work with reducer composition because you're effectively hardcoding reducer structure into the action objects.

@mapreal19

This comment has been minimized.

Show comment
Hide comment
@mapreal19

mapreal19 Dec 22, 2015

I see. Thank you for the quick feedback @gaearon 👍

I see. Thank you for the quick feedback @gaearon 👍

@negamaxi

This comment has been minimized.

Show comment
Hide comment
@negamaxi

negamaxi Apr 17, 2018

Well, personally I hate switch so I use plain objects instead:

const actionHandlers = {
  [ADD_TODO] (state, todo) {
    // do stuff
  },
  [REMOVE_TODO] (state, id) {
    // do stuff
  }
}

const reducer =  (state = initialState, action) => {
    const { type, payload } = action
    const actionHandler = actionHandlers[type]
    if (actionHandler) {
      return actionHandler(state, payload)
    }
    return state
  }

Well, personally I hate switch so I use plain objects instead:

const actionHandlers = {
  [ADD_TODO] (state, todo) {
    // do stuff
  },
  [REMOVE_TODO] (state, id) {
    // do stuff
  }
}

const reducer =  (state = initialState, action) => {
    const { type, payload } = action
    const actionHandler = actionHandlers[type]
    if (actionHandler) {
      return actionHandler(state, payload)
    }
    return state
  }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment