Philip Chmalts Philip Chmalts - 3 months ago 41
React JSX Question

Reusing actions across multiple reducers in redux

So I'm working on a large react/redux app and have been very happy with how simple it is manage state in redux. We are using a "ducks" style approach to reducers/action/action Creators to keep things relatively domain based. In addition, we try to keep ui state associated with the domain and most of the reducers have a similar structure. It looks something like this :

export default function home(state = initialState, action = {}) {
switch (action.type) {
case OPEN:
return {
...state,
ui: {
...state.ui,
[action.key]: true
}
};
case CLOSE:
return {
...state,
ui: {
...state.ui,
[action.key]: false
}
};
case SELECT_TAB:
return {
...state,
ui: {
selectedTab: action.selected
}
};
default:
return state;
}
}


We end up having the same actions repeated over and over for the ui, mainly toggles and setting what is displayed. Is there a way to keep the domain based ui within a reducer without having to add in the
OPEN
and
CLOSE
type statements for the ui in every reducer. Maybe what I'm thinking of is an anti pattern in redux but curious to see if anyone has run into this kind of issue before?

UPDATE:

I like the solution listed below but how would you extend the reducer to sort of contain a ui reducer within it.

combineReducers({
home: {createUiReducer('home', initialState), ...home}
})


That way you can have nested domain based ui without repeating all of the actions. Not sure how you would go about that because you would essentially be dynamically adding CASE statements.

Answer

Well you would need to namespace them with a prefix. If you are, like the most developers are, lazy, then create a helper function which would generate the reducers for you ;)

Like this one..

const createOpenCloseReducer = (prefix, initialState) => {
  const = ui = prefix + "_UI";

  return (state = initialState, action) => {
     switch(action.type) {
     case (prefix + "_CLOSE"):  
       return { ...state, [ui]: "CLOSED" }
     case (prefix + "_OPEN"):
       return { ...state, [ui]: "OPENED" }
     default: 
       return state
     }
  }
}

import { combineReducers, createStore } from 'redux';

const rootReducer = combineReducers({
   home: combineReducers({
      ui: createOpenCloseReducer("HOME", { "HOME_UI": "CLOSED" }),
      data: someOtherHomeDataReducer
   }),
   someOther: createOpenCloseReducer("OTHER", { "OTHER_UI": "CLOSED" })
})

store = createStore(rootReducer)

// use it..
store.dispatch({type: "HOME_CLOSE"})
store.dispatch({type: "OTHER_OPEN"})

// your state..
store.getState().home.ui // {HOME_UI: "CLOSED"}
store.getState().someOther // {OTHER_UI: "OPENED"}