New combineReducers doc is ok, but it does not explain that you have to name reducer as the party of the state it manages.
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
andcounter
-- 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"
Most helpful comment
Thanks for feedback, it's very valuable.
I want to stress that
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
andimport * as
work. They have nothing to do withcombineReducers
API per se.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:They don't even matter if you use
combineReducers
helper as long as you create the object yourself:You can do this many times.
Before:
You see? It's just functions calling functions. No magic “deep stuff”.
And you can use
combineReducers
many times too:The only part where function names matter is when you use
export
+import * as
to “obtain” an object you pass tocombineReducers
because _that's just howimport *
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.