Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

take away the huge switch block #883

Closed
yeatszhang opened this issue Oct 12, 2015 · 14 comments
Closed

take away the huge switch block #883

yeatszhang opened this issue Oct 12, 2015 · 14 comments

Comments

@yeatszhang
Copy link

I really hate the huge switch, so I write a util function to help write reducer in a better way. ( My English is not that good, but I will try to show my idea. )

example

we can write like this.

import { ADD_TODO, DELETE_TODO, EDIT_TODO, COMPLETE_TODO, COMPLETE_ALL, CLEAR_COMPLETED } from '../constants/ActionTypes';

const initialState = [{
  text: 'Use Redux',
  completed: false,
  id: 0
}];

export default createReducer({
  [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 !== action.id
  ),

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

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

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

source code

/**
 * an elegance way to write reducer
 * @param funcMap the functions map
 * @param initState initiate state
 * @returns {Function}
 */
function createReducer(funcMap, initialState) {
  if (!isPlainObject(funcMap)) {
    throw new Error('funcMap need to be a plain object');
  }

  return (state = initState, action) =>  map.hasOwnProperty(action.type) ? 
  funcMap[action.type](state, action) : 
  state;
}

features

  • simple, whether use or not depends on you.
  • won't worry about the variable name duplicated
  • can use destruct action and state
  • can use arrow function
  • hashTable is faster than switch when having a lot of cases
  • don't need annoying break and default
@johanneslumpe
Copy link
Contributor

@gaearon
Copy link
Contributor

gaearon commented Oct 12, 2015

Hi, thanks for sharing! Redux isn't opinionated about how you create the reducers, so indeed you can use any convention you like, including this one. There are cases where you want to match by something other than type though—for example, sometimes you want to respond to action containing a specific field, like action.error or action.response.

@yeatszhang
Copy link
Author

@johanneslumpe thanks for recommending this useful package. I like the way it simplify actionCreator and reducer. But I think it isn't necessary to use a reduce method to handle multiple actions. Maybe hash is enough and more efficient. But the little problem can't stop me using it ^ ^ . I'd like to have a discussion with the author and contribute to it.

@yeatszhang
Copy link
Author

@gaearon I'm really exciting for your reply cause I really like redux! Your opinion is right, and I found many packages have already actualized my idea. Even I found the same code in redux docs. haha~
My compony use redux from beta version and it works well. Wish redux be better~!

@mheiber
Copy link

mheiber commented Jan 4, 2016

@yeatszhang here is another way of avoiding switch which doesn't require a reducer factory:

const reducer = (state, action) => {

    const {amount} = action.payload;

    const handlers = {
        [INCREMENT]: () => state + amount,
        [DECREMENT]: () => state - amount
    };

    return handlers[action.type](amount);
};

Advantages are that you can effectively switch on whatever you like, and you can reuse variables defined above the handlers as needed.

@gaearon
Copy link
Contributor

gaearon commented Jan 4, 2016

There are a few problems there though: allocating functions on every call and not handling the "unknown action" case.

@mheiber
Copy link

mheiber commented Jan 6, 2016

@gaearon Good points. I guess switch isn't so bad. Thanks for your help!

@gaearon
Copy link
Contributor

gaearon commented Jan 6, 2016

It's not :-) People tend to obsess over unimportant details, and the disdain for switch seems like one of such cases.

@joaomilho
Copy link

Another alternative is http://ramdajs.com/0.21.0/docs/#cond

@fracalo
Copy link

fracalo commented Oct 12, 2016

IMHO switch blocks are really legible and are perfect for documentation where you need to learn how a reducer works;
but it's true that sometimes this expressiveness can become a little annoying...
anyway here's an alternative pattern :

const reducer = (state, action) => ({
  [INCREMENT]: state + action.payload,
  [DECREMENT]: state - action.payload
}[action.type] || state)

This looks quite good , only cons is you loose switch case fallback.

@Mistereo
Copy link

@fracalo your reducer calculates next states for each action in object (no matter for which it was called), this may lead to performance issues.
Also some of your actions may depend on payload shape and that may lead to another unexpected problems:

const reducer = (state, action) => ({
  [INCREMENT]: state + action.payload,
  [DECREMENT]: state - action.payload,
  [FOO]: state + action.payload.foo.bar,
}[action.type] || state)
reducer(0, { type: INCREMENT, payload: 1  })

(this will throw "Uncaught TypeError: Cannot read property 'bar' of undefined")

@fracalo
Copy link

fracalo commented Oct 12, 2016

@Mistereo you're right, once the object is initialized the properties get evaluated so depending on the calculations you need to do it can be way more expensive.

You could make methods out of each property:

const reducer = (state, action) => {
  let o = {
    [INCREMENT]: () => state + action.payload,
    [DECREMENT]: () => state - action.payload,
    [FOO]: () => state + action.payload.foo.bar,
  }[action.type]
  return o ? o() : state
}

but it's starting to get a little noisy.

@ecancino
Copy link

ecancino commented Jul 13, 2017

@fracalo You can always create a helper, this is the one that I use: createReducer

const { createStore, combineReducers  } = require('redux')
const { prop, identity, defaultTo, add, subtract, concat, always } = require('ramda')

const createReducer = (actions, INITIAL) => 
    (state = INITIAL, { type, payload }) => 
        defaultTo(identity, prop(type, actions))(state, payload)

const count = createReducer({ INCREMENT: add, DECREMENT: subtract }, 0)
const list = createReducer({ PUSH: concat, RESET: always([]) }, [])

const { subscribe, dispatch, getState } = createStore(combineReducers({ count, list }))

subscribe(() => console.log(getState()))

dispatch({ type: 'PUSH', payload: [1, 2, 3] })
dispatch({ type: 'DECREMENT', payload: 10 })

@encodedwitch

This comment has been minimized.

@reduxjs reduxjs locked and limited conversation to collaborators Aug 13, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

9 participants