5

I've built a global state provider context, and a reducer with the useReducer Hook. I'm realizing that combining multiple reducers like I would in redux is problematic. Is there a good way to do this? I have seen people importing combineReducers from redux itself, and that seems like it kind of defeats the point. Does anyone have any insight on this?

Dave Funk
  • 77
  • 8
  • Sounds like you should consider start using redux instead of the hooks and enjoy some nice tooling ;) Although the redux hook has less overhead for simple usages, I don't see the benefit for multiple combined reducers. That being said, a plain combineReducers is fairly simple to implement. – Markus Mar 09 '19 at 00:34
  • you're right. However, I started this particular project to learn hooks INSTEAD of redux. Redux makes this a lot simpler, but I dont want to rely on it. – Dave Funk Mar 12 '19 at 23:56

1 Answers1

6

Not sure this is what you're looking for, but I have used something like below to combine multiple reducers. It actually reduces the reducers. Not actually like redux combineReducers with key/value.

const reduceReducers = (...reducers) => (prevState, value, ...args) =>
  reducers.reduce(
    (newState, reducer) => reducer(newState, value, ...args),
    prevState
  );

I would be used like:

function reducerA(state, action) {
  switch (action.type) {
    case "increment":
      return { ...state, count: state.count + 1 };
    case "decrement":
      return { ...state, count: state.count - 1 };
    default:
      return state;
  }
}

function reducerB(state, action) {
  switch (action.type) {
    case "double":
      return { ...state, count: state.count * 2 };
    case "halve":
      return { ...state, count: state.count / 2 };
    default:
      return state;
  }
}

export default reduceReducers(reducerA, reducerB);

Then the Component:

import reducers from "./reducers";

function Counter({ initialState = { count: 1 } }) {
  const [state, dispatch] = useReducer(reducers, initialState);
  return (
    <>
      Count: {state.count}
      <button onClick={() => dispatch({ type: "increment" })}>+</button>
      <button onClick={() => dispatch({ type: "decrement" })}>-</button>
      <button onClick={() => dispatch({ type: "double" })}>x2</button>
      <button onClick={() => dispatch({ type: "halve" })}>/2</button>
    </>
  );
}
Corey
  • 5,818
  • 2
  • 24
  • 37
  • gonna give this a shot. this looks like exactly what i was looking for. I'll let you know how it works out! i attempted something similar but was unsuccessful. I am a junior, and I am very familiar with map/filter but still sometimes struggle with the reduce api. – Dave Funk Mar 08 '19 at 22:05
  • 1
    this is the correct answer. I was able to do it after spending some time with curried functions. I am discovering through doing all of this though that Redux really is a lot simpler to get the same result. This method has issues like handling initialState. short of using object spread and merging seperate initialStates, or maintaining a giant object in the file I have the context declaration/provider in, there isn't a good way. – Dave Funk Mar 09 '19 at 00:34
  • @DaveFunk For handling initialState We can combine multiple states as below ```const initialState = { reducerAState, reducerBState }``` and then pass to useReducer – akshay May 20 '19 at 15:02