Redux: `combineReducers` doc does not explain how t gueses which part of state to pass to reducer

Created on 8 Aug 2015  ·  7Comments  ·  Source: reduxjs/redux

New combineReducers doc is ok, but it does not explain that you have to name reducer as the party of the state it manages.

docs

Most helpful comment

Thanks for feedback, it's very valuable.

I want to stress that

Convention are part of API

is not really true.

We should probably just avoid import * in the docs because people assume it's integral part of the API when it's not at all, and it's just a convenient shortcut I use.

Function names only matter because of how ES6 export and import * as work. They have nothing to do with combineReducers API per se.

Btw, I'm still puzzled how to pass subpart of state to reducer. Should I camelCase join them or something? state={owner: {name: 'John'} } → export function ownerName(state = [], action)?

No :-). It's not a magical API. combineReducers(object) combines several reducers into one, and passes parts of state to its values by the keys you provide. It only does this exactly one level deep. There's no “build whole tree magic”. It's up to you to split a reducer into more functions:

// Function names don't matter!
function processA(state, action) { ... }
function doSomethingWithB(state, action) { ... }

function reducer(state = {}, action) {
  return {
    // Because you call them!
    a: processA(state.a, action),
    b: doSomethingWithB(state.b, action)
  };
}

// Not using combineReducers
let store = createStore(reducer);

They don't even matter if you use combineReducers helper as long as you create the object yourself:

// Function names don't matter!
function processA(state, action) { ... }
function doSomethingWithB(state, action) { ... }

/*
function reducer(state = {}, action) {
  return {
    a: processA(state.a, action),
    b: doSomethingWithB(state.b, action)
  };
}
*/
let reducer = combineReducers({
  a: processA,
  b: doSomethingWithB
});

let store = createStore(reducer);

You can do this many times.

Before:

// Function names don't matter!
function processSomePartOfA(state, action) { ... }
function doSomethingWithOtherPartOfA(state, action) { ... }

function processA(state, action) {
  return {
    // Because you call them!
    somePart: processSomePartOfA(state.somePart),
    otherPart: doSomethingWithOtherPartOfA(state.otherPart)
  }
}
function doSomethingWithB(state, action) { ... }

function reducer(state = {}, action) {
  return {
    // Because you call them!
    a: processA(state.a, action),
    b: doSomethingWithB(state.b, action)
  };
}

// Not using the helper
let store = createStore(reducer);

You see? It's just functions calling functions. No magic “deep stuff”.
And you can use combineReducers many times too:

// Function names don't matter!
function processSomePartOfA(state, action) { ... }
function doSomethingWithOtherPartOfA(state, action) { ... }

/*
function processA(state, action) {
  return {
    somePart: processSomePartOfA(state.somePart),
    otherPart: doSomethingWithOtherPartOfA(state.otherPart)
  }
}
*/
let processA = combineReducers({
  somePart: processSomePartOfA,
  otherPart: doSomethingWithOtherPartOfA
});

function processA(state, action) { ... }
function doSomethingWithB(state, action) { ... }

/*
function reducer(state = {}, action) {
  return {
    a: processA(state.a, action),
    b: doSomethingWithB(state.b, action)
  };
}
*/
let reducer = combineReducers({
  a: processA,
  b: doSomethingWithB
});

let store = createStore(reducer);

The only part where function names matter is when you use export + import * as to “obtain” an object you pass to combineReducers because _that's just how import * works_! It puts things in object based on their export keys.

I think my biggest mistake here is assuming reader is familiar with named exports.

All 7 comments

It kinda says it if you read it completely, without skipping:

The resulting reducer calls every child reducer, and gather their results into a single state object. The shape of the state object matches the keys of the passed reducers.

But I agree this needs to be more prominent, as people often miss this sentence.
Would changes from #399 help you?

I`d prefer less computer sciency, explicit note about this convention right after example:

Note that reducers are named todos and counter -- exactly as the parts of the state we're passing to them.

To pass part of the state to reducer Redux employs _convention_ -- reducer should be named exactly as a part of the state you wish to pass to it.

I think #399 is good but it's an explanation for _Flux users_. I'm coming to Redux fresh of the boat. Some form of my text would be more helpful for casual JavaScript developers.

Important idea here is: Convention are part of API. It's equally important to createRedux, createDispatcher and any other API function. Always explicitly state conventions because they are a part of API.

Btw, I'm still puzzled how to pass subpart of state to reducer. Should I camelCase join them or something? state={owner: {name: 'John'} }export function ownerName(state = [], action)?

Thanks for feedback, it's very valuable.

I want to stress that

Convention are part of API

is not really true.

We should probably just avoid import * in the docs because people assume it's integral part of the API when it's not at all, and it's just a convenient shortcut I use.

Function names only matter because of how ES6 export and import * as work. They have nothing to do with combineReducers API per se.

Btw, I'm still puzzled how to pass subpart of state to reducer. Should I camelCase join them or something? state={owner: {name: 'John'} } → export function ownerName(state = [], action)?

No :-). It's not a magical API. combineReducers(object) combines several reducers into one, and passes parts of state to its values by the keys you provide. It only does this exactly one level deep. There's no “build whole tree magic”. It's up to you to split a reducer into more functions:

// Function names don't matter!
function processA(state, action) { ... }
function doSomethingWithB(state, action) { ... }

function reducer(state = {}, action) {
  return {
    // Because you call them!
    a: processA(state.a, action),
    b: doSomethingWithB(state.b, action)
  };
}

// Not using combineReducers
let store = createStore(reducer);

They don't even matter if you use combineReducers helper as long as you create the object yourself:

// Function names don't matter!
function processA(state, action) { ... }
function doSomethingWithB(state, action) { ... }

/*
function reducer(state = {}, action) {
  return {
    a: processA(state.a, action),
    b: doSomethingWithB(state.b, action)
  };
}
*/
let reducer = combineReducers({
  a: processA,
  b: doSomethingWithB
});

let store = createStore(reducer);

You can do this many times.

Before:

// Function names don't matter!
function processSomePartOfA(state, action) { ... }
function doSomethingWithOtherPartOfA(state, action) { ... }

function processA(state, action) {
  return {
    // Because you call them!
    somePart: processSomePartOfA(state.somePart),
    otherPart: doSomethingWithOtherPartOfA(state.otherPart)
  }
}
function doSomethingWithB(state, action) { ... }

function reducer(state = {}, action) {
  return {
    // Because you call them!
    a: processA(state.a, action),
    b: doSomethingWithB(state.b, action)
  };
}

// Not using the helper
let store = createStore(reducer);

You see? It's just functions calling functions. No magic “deep stuff”.
And you can use combineReducers many times too:

// Function names don't matter!
function processSomePartOfA(state, action) { ... }
function doSomethingWithOtherPartOfA(state, action) { ... }

/*
function processA(state, action) {
  return {
    somePart: processSomePartOfA(state.somePart),
    otherPart: doSomethingWithOtherPartOfA(state.otherPart)
  }
}
*/
let processA = combineReducers({
  somePart: processSomePartOfA,
  otherPart: doSomethingWithOtherPartOfA
});

function processA(state, action) { ... }
function doSomethingWithB(state, action) { ... }

/*
function reducer(state = {}, action) {
  return {
    a: processA(state.a, action),
    b: doSomethingWithB(state.b, action)
  };
}
*/
let reducer = combineReducers({
  a: processA,
  b: doSomethingWithB
});

let store = createStore(reducer);

The only part where function names matter is when you use export + import * as to “obtain” an object you pass to combineReducers because _that's just how import * works_! It puts things in object based on their export keys.

I think my biggest mistake here is assuming reader is familiar with named exports.

I think my biggest mistake here is assuming reader is familiar with named exports.

I think, assuming reader is familiar with ES6 at all is a stretch. But that's what pushes people to learn and to figure this stuff out. So it's actually a good thing when reading is ahead of the reader's knowledge and experience.

We're changing examples to explicitly call combineReducers in reducers/index so hopefully it's going to make more sense from now on: https://github.com/gaearon/redux/pull/473

Closing, as this seems to be better addressed in the current docs.
And we don't use import * in docs anymore either.

Do you really think that embedding an implicit behavior like that is a good coding practice ?

For me it obfuscates the code and combineReducer should not exist, it adds an unnecessary step of abstraction and complexifies the process.

When you write a framework, an important feature like this one should be easily understandable, that is obviously not the case, e.g. the "magic" feeling.

P.S. : I come from the official Redux documentation : "It's not magic"

Was this page helpful?
0 / 5 - 0 ratings

Related issues

benoneal picture benoneal  ·  3Comments

vraa picture vraa  ·  3Comments

cloudfroster picture cloudfroster  ·  3Comments

parallelthought picture parallelthought  ·  3Comments

timdorr picture timdorr  ·  3Comments