-
-
Notifications
You must be signed in to change notification settings - Fork 15.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
RFC: Make combineReducers() shape-agnostic #1792
Comments
|
Ooo. I _like_ this idea. 👍 💯 |
Deep conversion is not necessarily what you want (you might have nested plain object reducers). Also it would be much slower in the common case. |
Fair enough. In my experience, shallow conversion commonly leads to errors (eg methods like |
To expand on my comment: this seems useful, simplifies interop, and is consistent with the "sensible default with optional customization" approach used by |
Since it's pluggable you would be able to customize it. But it would be super confusing if applying it at the root level broke third-party reducers from another packages, for example. Usually combineReducers() has no effect on the state of the tree below. It just manages one level deep. It would be strange if suddenly it took over the state returned by nested reducers and transformed it. So while the approach you suggest is possible, it isn't something we'd encourage. |
Don't use fromJS it's really expensive operation in immutable lib. |
There are lots of good reasons to use it, just not in this context as Dan explained. I've struck-through my suggestion so others won't read it and not notice the below discussion. :) |
Big 👍 . Having this in redux will provide guidance to other projects for how to support this scenario, e.g. this has been a long standing question in redux-persist. I believe the configuration would also need an iterator method. |
I've personally never been a fan of these sorts of pre and post processing configs. js-csp has something like this I think. I prefer something more functional and composable I think |
It's fine that combineReducers has its utility for delegating responsibility for keys of objects, I just wish the Redux docs showed a simple reducer to be delegation driven as well, ala this gist or simply:
|
@deanius: switches and maps are equivalent. Both have their uses. The docs teach switches first for clarity, with map lookups shown later as a shorter approach. See http://redux.js.org/docs/recipes/ReducingBoilerplate.html and http://redux.js.org/docs/FAQ.html#reducers-use-switch. Would appreciate any input you have on topics for a "Structuring Reducers" docs page in #1784. Docs PRs also welcome :) |
@Blesh I'm not a big fan of them either but it sounds better than people having to reimplement combineReducers warnings (often lagging behind the main version) by hand. If you have specific other ideas please suggest them though! |
Not sure if it fits in with this RFC, but while we're talking about |
So this would mean you could pass, say, an const reducers = Map({ foo, bar, baz });
const rootReducer = combineReducers(reducers) If that's the case, wouldn't it also need a way to query all valid keys from the passed instance to build up the |
@aweary : Ah... no, as I understand the proposal, the idea is that this would modify the actual handling of return values from the sub-reducers. So, rather than |
So the advantage here is that your top level state returned from the |
I'm plus one on this as well. Iterator or keys config seems necessary though since many enhancers examine the top level keyspace and rely on the assumed plain object nature of the atom today |
Quickly threw something together for this with a spec using the ImmutableJS example. Thing to note is that for this to work with ImmutableJS (as far as I can tell) if the user wants to provide a set method it will have to return the new state, it can't rely on side-effects being applied to the state object it gets passed. Also I wonder if the interface should merge with the default options or require that you provide all three if you want to do anything differently. EDIT: Also it seems like for it to support ImmutableJS (for the individual reducers) the state comparison would be sort of odd. It would always return a new state unless it did a deep comparison. Should that also be configurable then?
|
Could we put those warnings into a reducer enhancer, to make it easier to replace |
I can speak for #1768. If we're just talking about the ability to define custom create and get semantics (ie. borkxs@7b3315e), this doesn't conflict with my modifications, though I would have to merge those changes into mine or vice-versa. |
If the store was an immutableJs object, it would be much faster to have all of the state changes wrapped in a withMutations block to avoid having to rewrite more than necessary. I'm not sure if there's a good way to write a generic combineReducers that could perform this optimization though. Is it worth considering that providing a solution that works out of the box for any state type will discourage people from writing a combineReducers designed for their use case? I feel like 90% of cases where this new proposed method was used for an immutablejs state, you'd be better off just writing one optimized for immutablejs anyway. |
I don't think the intent here is to do anything regarding deeper updates. |
Huh. Interestingly, rt2zz/redux-persist#113 looks like basically the same concept, and that issue is actually waiting on this one to be resolved :) |
@markerikson When you say deeper updates, did you just mean updating lower portions of the state tree? I was just saying that this block of code from combineReducers (edited to use the generic set get and create options) var nextState = options.create({})
for (var i = 0; i < finalReducerKeys.length; i++) {
var key = finalReducerKeys[i]
var reducer = finalReducers[key]
var previousStateForKey = options.get(state, key)
var nextStateForKey = reducer(previousStateForKey, action)
if (typeof nextStateForKey === 'undefined') {
var errorMessage = getUndefinedStateErrorMessage(key, action)
throw new Error(errorMessage)
}
nextState = options.set(nextState, key, nextStateForKey) //this creates a new intermediate state object every loop iteration if your state is immutable
hasChanged = hasChanged || nextStateForKey !== previousStateForKey
} would be worse than this block written strictly for immutablejs. var nextState = Map().withMutations(initState => { //this prevents the issue with the above code
for (var i = 0; i < finalReducerKeys.length; i++) {
var key = finalReducerKeys[i]
var reducer = finalReducers[key]
var previousStateForKey = state[key]
var nextStateForKey = reducer(previousStateForKey, action)
if (typeof nextStateForKey === 'undefined') {
var errorMessage = getUndefinedStateErrorMessage(key, action)
throw new Error(errorMessage)
}
nextState.set(key, nextStateForKey)
hasChanged = hasChanged || nextStateForKey !== previousStateForKey
}
}); because of all of the intermediate objects created. I'm not sure there's a good way to optimize for all libraries, but I feel like providing one that "works" with performance problems will discourage people from writing a combineReducers that actually performs well for their use case. |
Hmm. Interesting point. For what it's worth, glancing at two existing implementations of Issue #548 is somewhat relevant as well. |
Yea I've seen it in other places too. redux-loop/redux-loop#51 I always viewed the combineReducers methods included with any libraries as more of a guideline, because writing a good generic one seems like a very difficult problem and it's not that difficult to write one for any specific project. |
So, part of the reason for wanting to change this is that since this |
At the very least the documentation could reflect that this version of combineReducers is probably not ideal except for a very basic use case. |
Started looking at But then looking at the rest of it, to be able to do this check would mean the user would have to provide a method to iterate over the keys of their state ( I could be missing something but my overall impression is that "shape-agnostic" and "supports ImmutableJS" don't have a lot of overlap. |
@borkxs the object passed to combineReducers is usually a plain js object, even if your state is immutable. it's not the reducer, just a map of names (of subtrees) to reducers. |
@bdwain I'm talking about the method that validates the shape of the previous state, not about checking the input to |
Ah my mistake. Yea On Jun 15, 2016, 6:08 PM -0500, Erik Michaelsonnotifications@github.com, wrote:
|
What would be the next actionable step I can help you with? |
Rich Hickey gave a talk at Strangeloop about Transduceres. It seems pretty relevant to this discussion. |
Realistically, I don't think this is ever going to happen. I still sorta think it'd be neat if |
I propose we should make
combineReducers()
work with ImmutableJS and any other libraries. This way all the checks it makes would work out of the box, but the actual state transformation for associating the values would be swappable.I’m thinking something like
combineReducers(reducers, [options])
.By default
options
is something likebut you could supply something like
Does this make sense to anyone?
If you’d like to work on a PR to make a proof of concept, please comment here and submit it!
If you think it’s a bad idea, please write here as well.
The text was updated successfully, but these errors were encountered: