2

I am new to Redux and am trying to figure out a scaleable way to setup my projects folder/file structure.

Lets say we have a file structure that looks like this:

root/modules/Todos/reducers

In the root of the project there lives a 'rootReducer.js' file which utilizes 'combineReducers()' to create a top-level implementation of the state tree:

[rootReducer.js]

import { combineReducers } from 'redux';

import todos from './modules/Todos/reducers/index.js';

export default combineReducers({
  todos: todos
});

Inside of the 'reducers' folder for each module there are multiple reducers:

[root/modules/Todos/reducers]

>index.js
>Todos__addItem
>Todos__removeItem

The 'index.js' file imports all of the reducers for that module and exports a single object:

[index.js]

import { combineReducers } from 'redux';

import addItem from './Todos__addItem.js';
import removeItem from './Todos__removeItem.js';

export default const todos = combineReducers({
  addItem: addItem,
  removeItem: removeItem
});

Is this the correct use of 'combineReducers()'?

Does this pattern make sense when developing a large scale application?

What are (if any) potential pitfalls that come along with this pattern?

Thanks!

Celestriel
  • 377
  • 1
  • 5
  • 12

2 Answers2

2

It's definitely not the correct usage of combineReducers. combineReducers is used to delegate management of a specific slice of state to a given function. Your example would actually create slices of state named addItem and removeItem, when what you really want to do is to update the same todos slice of state using those functions in different ways depending on which action was dispatched.

The Redux docs section on "Structuring Reducers" has some information that should help with this, including the section on Using combineReducers.

markerikson
  • 63,178
  • 10
  • 141
  • 157
  • I appreciate the reply and work you've done with your writings! In regards to the 'export todos' statement in 'index.js', do you know how I might refactor that to achieve what I am looking for? I'm having troubles figuring out the right way to implement it! Should I be exporting the collection of sub-reducers as a function, or just a plain object? – Celestriel Aug 16 '16 at 22:44
  • The `todo` handling would be where you'd see the typical switch statement or lookup table for the various update cases. See http://redux.js.org/docs/recipes/ReducingBoilerplate.html#generating-reducers for a relevant example. Also, in case you haven't seen them yet, Dan has two great video tutorial series where he demonstrates a number of reducer approaches: https://egghead.io/series/getting-started-with-redux and https://egghead.io/series/building-react-applications-with-idiomatic-redux . – markerikson Aug 17 '16 at 00:49
0

example from https://github.com/suin/redux-multiple-reducers-example

import   {counter1, counter2 } from "../../reducers/index"
import CounterApp from "../containers/CounterApp";


const rootReducer = combineReducers({
  one:counter1 ,
  two:counter2
});

const store = createStore(rootReducer);

class App extends  React.Component{
  render() {
    return (
      <Provider store={store}>
          <CounterApp />
      </Provider>
    );
  }

Counter1 view

import * as counter1Actions from "../../actions/counter1Actions";

@connect(state => ({
  counter1: state.one
}))
export default class Counter1 extends  React.Component{

  static propTypes = {
    counter1: PropTypes.number.isRequired
  }

  componentDidMount() {
    console.info("counter1 component did mount.");
  }

  onClick() {
    console.info("counter1 button was clicked.");
    const action = bindActionCreators(counter1Actions, this.props.dispatch);
    action.increment();
  }

  render() {
    return (
      <div>
        <h1>Counter 1</h1>
        <button onClick={::this.onClick}>increment</button>
        <div>Total: <span>{this.props.counter1}</span></div>
      </div>
    );
  }
}

Counter2 view

import * as counter2Actions from "../../actions/counter2Actions";

@connect(state => ({
  counter2: state.two
}))
export default class Counter2 extends  React.Component {
  static propTypes = {
    counter2: PropTypes.number.isRequired
  }

  componentDidMount() {
    console.info("counter2 component did mount.");
  }

  onClick() {
    console.info("counter2 button was clicked.");
    const action = bindActionCreators(counter2Actions, this.props.dispatch);
    action.increment();
  }

  render() {
    return (
      <div>
        <h1>Counter 2</h1>
        <button onClick={::this.onClick}>increment</button>
        <div>Total: <span>{this.props.counter2}</span></div>
      </div>
    );
  }
}

CounterApp

import Counter1 from "../components/Counter1";
import Counter2 from "../components/Counter2";

 class CounterApp extends  React.Component{
   render() {
     return (
       <div>
         <Counter1/>
         <Counter2/>
       </div>
     );
   }
 }

reducer

export default function counter1(state = initialState, event) {
  switch (event.type) {
    case "COUNTER1_INCREMENTED":
      console.info(`counter1 ack ${event.type}: event =`, event);
      return state + 1;
    default:
      console.warn("counter1 ack unknown event: state =", state, "event =", event);
      return state;
  }

export default function counter2(state: Object = initialState, event: Object): Object {
  switch (event.type) {
    case "COUNTER2_INCREMENTED":
      console.info(`counter2 ack ${event.type}: event =`, event);
      return state + 1;
    default:
      console.warn("counter2 ack unknown event: state =", state, "event =", event);
      return state;
  }
}
zloctb
  • 10,592
  • 8
  • 70
  • 89