We really, _really_ need a page talking about approaches, guidelines, and suggestions for organizing reducer logic. I've been saying I want to write this for a while, but have been too busy so far. I'm still up for it, but further suggestions and/or offers of help would be appreciated.
Initial sketch of possible topics:
(state, action) -> newState, immutable updates, and one other item I've said before but I'm not remembering at the momentcombineReducers is simply a utility function for the common use case of delegating update logic based on state domain organization, and not a requirementcreateReducers is, effectively, defining the name/shape of your state (not always clear when using ES6 object literal shorthand - naming of imported reducer functions matters!)combineReducersRandom related links:
Some overlap with the performance/optimization discussions in https://github.com/reactjs/redux/issues/1783 , the store organization mention in http://redux.js.org/docs/FAQ.html#organizing-state-nested-data , and the concepts in http://redux.js.org/docs/recipes/ComputingDerivedData.html .
This may help a bit https://github.com/nosovsh/reduceless
@BananaBobby : thanks for the link, but I'm looking to document and clarify idiomatic Redux usage.
Reactiflux quote:
[8:33 PM] Francois Ward: where state lives is more a matter of how you plan on manipulating it than anything else.
[8:34 PM] Francois Ward: so if you have, let say a "loading: true/false" flag that -always- updates when you set some entity, it probably should live there.
[8:34 PM] Francois Ward: if its completely independant, changes separately, etc, then it should be its own thing.
[8:34 PM] Francois Ward: as you develop apps you often will refactor that stuff.
[8:35 PM] Francois Ward: since the relationship between actions, components and reducers is completely loose and maleable, it can change all the time.
I could use some advice around using combineReducers when some slices of the state are static and don't need to be reduced. Here is a contrived example:
// This is what I'd like the state to look like:
{
isUserSignedIn: true, // static, hydrated data - doesn't need a reducer
todoTemplates: [...], // same
todos: [...] // hydrated then manipulated - needs a reducer
}
// This is one way of making it work:
const reducer = combineReducers({
isUserSignedIn: (state = false) => state, // basically a noop reducer
todoTemplates: (state = []) => state, // same
todos: todosReducer
});
The solution above is not very practical and scalable when the number of static slices of the state increases. I can think of a couple of other ways to make this work, but neither really stands out as the right solution:
combineReducer => not scalablestatic, and use a noop reducer on that => scalable, but static is kind of ugly and doesn't describe what the data actually is (and there may not be a name that does, since all the static sub-keys may not have anything in common).Not really intended as a Q&A thread, but that's definitely an interesting question I haven't considered before.
My initial thought was that a static value like that shouldn't really be in Redux state, but given that it's something server-hydrated, I can see a point to it. I've actually got some static-ish data coming back in my host page (user full name, etc), and at the moment a couple of my components are just referencing window.theVariableFromTheServer, but that's an intriguing idea now that I think about it.
I would think that putting it under a staticData key with a noop reducer would be reasonable. Suppose the big question is how much of this type of data is there, and where is it coming from.
@axelboc here are a couple of options:
combineReducerscombinedReducers = combineReducers({
todos: todosReducer
})
rootReducer = function(state, action) {
return Object.assign({}, state, combinedReducers(state, action));
}
Also see this issue: https://github.com/reactjs/redux/issues/1457
In particular, Dan's comment here: https://github.com/reactjs/redux/issues/1457#issuecomment-191347804
Wow, thanks! This definitely should go in the docs. I'm going with your second solution @naw, as dealing with static data the same way as dynamic data will lead to the cleanest code in my situation.
This custom root reducer is so straightforward, logical and convenient, that it comes a little as a surprise to me that it's not already a built-in feature of combineReducers (perhaps an opt-in feature). Anyway, I'm sure there are reasons why it's not, and this is definitely not the thread for it... So thanks for your help!
Two thoughts:
combineReducers is _just_ a utility function for the most common use case. Just like any other part of your application code, you can break things down into functions any way it makes sense to you, just that with reducers as a whole have to obey the basic rules of (state, action) -> newState and immutable updates. Certainly doesn't mean that _every_ reducer function must have the exact (state, action) signature, though, just that the final result needs to be put together that way.Glad that you got things working!
@markerikson Regarding the original intent of this issue you created --- I definitely agree a page about structuring reducers would be helpful.
I also strongly agree with your statement:
Emphasis that combineReducers is simply a utility function for the common use case of delegating update logic based on state domain organization, and not a requirement
Personally I think one thing that might help is standardizing terminology regarding things like "root reducer", "sub-reducers", "reducers", "helper functions", "reducer factories", "state", "slice", etc. In some cases we use the same terms to refer to different things, and other case we use different terms to refer to the same thing.
For example, what _should_ we call the functions that manage a particular slice of state? Are they "reducers", "sub-reducers", both, neither? Is a function considered a reducer merely because it "helps" the rootReducer manage state in some way, or is it _only_ a reducer if it has a certain method signature?
Moving on to combineReducers in general:
I think you could make the argument that combineReducers isn't really "core" redux. It's a high-level organization pattern that plugs in to the main redux engine (single store with dispatcher and subscriptions). There are other patterns that arguably work just as well, and as you've pointed out, all that matters is that the rootReducer itself exposes the correct method signature.
We do need to help people understand that combineReducers is just a pattern, and perhaps wrap some standard terminology around this pattern as well as other patterns, so that we have a language for the ecosystem as a whole. For example, if a 3rd party provides a "sub-reducer", it needs a way to communicate how it expects to be embedded into the rootReducer. Is it going to be embedded via combineReducers within a given key/slice, or is it going to be embedded at the top-level, or is it something else?
It's easy for 3rd party tools just to "assume" everyone is using combineReducers, even though combineReducers is not required. Sometimes it's feasible for a 3rd party tool to accommodate more than one pattern at once. For example, https://github.com/reactjs/react-router-redux can accommodate combineReducers _and_ reduce-reducers : https://github.com/reactjs/react-router-redux/pull/183
Maybe it would be helpful to highlight some of these other patterns alongside combineReducers to make it more obvious that combineReducers is _not_ required?
Just trying to brainstorm with you...
@naw : excellent comments, and definitely some of the stuff that's going through my head. Not sure I have specific or definitive answers at the moment, but please keep tossing out those kinds of ideas for discussion.
Okay. I am finally, _finally_ sitting down to start work on this. To be honest, there's so many related topics involved here that I'm not exactly sure how to address things.
Initial empty doc page pushed to https://github.com/markerikson/redux/blob/structuring-reducers-page/docs/recipes/StructuringReducers.md . I'll try to update it as I go. Suggestions and feedback wanted!
I was able to get started on this and crank out a decent first chunk of work. Unfortunately, it basically duplicates a large portion of what's already in the "Reducers" docs page, and Dan's videos.
There's a couple reasons for that. First, I feel like I need to be covering things from first principles. Stuff like what "mutations" and "immutability" are, how to update data immutably, demoing how to refactor a reducer into smaller functions, etc.
I'm probably going to take a suggestion from @naw and prefix this with a page saying "Go read articles X, Y, and Z first, and make sure you totally understand them." (Loosely, "you must be THIS tall to ride".) For immutability, http://reactkungfu.com/2015/08/pros-and-cons-of-using-immutability-with-react-js/ is fantastic, http://wecodetheweb.com/2016/02/12/immutable-javascript-using-es6-and-beyond/ and http://t4d.io/javascript-and-immutability/ are pretty good.
Anyway, I'm also figuring on breaking this up into subpages. Rough sketch:
combineReducerscombineReducersAnd, uh, whatever else I come up with.
One other side thought that I'm jotting down here for reference, but won't create an issue for yet: might be useful to have an "Idiomatic Redux Architecture" page or something, that talks about stuff like using mapDispatch and binding to keep your components "unaware" of Redux, etc.
Thank you for your effort to educate the community. It's really great to have access to so much material while trying to learn new stuff. I have read through most of the articles/posts that you have written and linked to.
I'm completely new to Redux and I'd really like to read some best practice on what to store in the store. Maybe such information would fit in your new document? I might be way off here or come out as a complete fool, but I'll try to explain what I'm having a hard time to grasp by describing the flow in a new project I'm working on.
What data goes where? I think I'd like the local database to be the truth. After some usage time this database will hold most of the relevant data fetched from the server as an offline cache. After a while I will throw out old data though.
I assume that Redux will serve as kind of a secondary cache? I.e. it won't hold data that is not loaded/used in this app session, would it? Or should Redux hold all data? If that is the case the local database would be an exact copy of the Redux store and the Redux store would potentially be quite big.
I have a lot of image handling in the app. Images will be referenced from the objects in the Redux store by UUID (also a key in Postgres DB). Local disk on the device serves as an image cache with UUIDs as filenames. I shouldn't cache binary data in the Redux store should I? Or is there a balance somewhere? Maybe Base64 encoded thumbnails can live in the store? But not 5 Mb jpg files, for sure..
In what order should I fetch data?
A. Container needs props. Props fetched from local DB if available, otherwise from REST API. After fetching, the data gets duplicated in local db and Redux store.
or B. Container needs props. Props fetched from local DAO which handles local DB as cache
And what about writing local changes to local database for later synchronization with server? Should I have a DAO which subscribes to store changes and saves changes to database. And at the same time have a synchronizer subscribing to changes for sending to the server if network is available?
I'm having a hard time to envision how Redux fits in with a local database. Hopefully I'm overthinking stuff and instead there is a really simple solution to all this =)
And some thoughts about immutability (a concept quite new to me as well)..
If I understand correctly I could make a shallow copy of the state in the reducer in order to save on the time it would take to make a deep copy? This would be done only to get a new object reference and fool the data consumers that I have provided a shiny new object. But in fact I have the same old referenced data deeper down in the object. Is this really immutability? I'm perfectly fine with whatever, but I would really like to read some more on this. How much of a state-slice should be immutable for the state to be considered immutable? I might have misunderstood something here though...
Btw, thank you very much for your answer at SO about store structure.
After some sleep I now see that I might have hijacked your thread with my own questions. Please take the above questions as input and suggestion on what to include in your StructuringReducers.
Yeah, bit of a digression there. You might want to drop into the Reactiflux chat channels and ask some of the questions there.
For immutability, the key is that you never modify the contents of an existing object reference. If you have an = sign for an assignment, and the thing on the left side of the = isn't already a copy, you're probably directly mutating data. So yes, that means making a shallow copy, and overwriting some of the fields in that copied object. See the list of articles I have linked for this topic at https://github.com/markerikson/react-redux-links/blob/master/immutable-data.md. You probably also want to read through the Redux FAQ, which includes topics like deep-cloning vs shallow-cloning: http://redux.js.org/docs/FAQ.html#performance-clone-state
While I don't want to go over the entire concept of updating data immutably, it would probably be useful to add a page that demonstrates various recipes and approaches for specific tasks. For example, updating a nested object field using Object.assign vs object spreads vs a couple immutable update utilities vs a Ramda lens.
Another related topic: "thin" reducers vs "thick" reducers, per http://redux.js.org/docs/FAQ.html#structure-business-logic . I very much tend towards 'thick" reducers myself, but I've seen a number of utilities and libs that treat part or all of a Redux store as a simple key/value storage, with reducer logic along the lines of return {...state, ...action.payload}. I know @phoenixmatrix seems to prefer that sort of approach. That obviously changes reducer logic structure considerably.
For anyone actually paying attention to this: I'm on a work trip atm, got nothing on my schedule for the weekend, and intend to spend basically the entire weekend trying to crank out more of these docs. We'll see how much progress I make.
Immutable data update tools:
Was able to crank out first drafts for "Using combineReducers" and "Beyond combineReducers" yesterday. Looks like the next couple topics on my list are dealing with normalized data.
I should also include something about the approach Dan uses in his first video series, where he defines a reducer function for a _single_ todo, and then reuses it in a couple different contexts to do updates ( https://github.com/tayiorbeii/egghead.io_redux_course_notes/blob/master/08-Reducer_Composition_with_Arrays.md )
Normalized data reading:
@markerikson this thread is really informative, thanks for all the links
@mmazzarolo : Thanks. If you have any specific feedback about the current WIP versions of the doc pages, I'd definitely like to hear it.
Thanks @markerikson for this awesome overview of structuring reducers! Looking forward to reading https://github.com/markerikson/redux/blob/structuring-reducers-page/docs/recipes/reducers/07-UpdatingNormalizedData.md ;)
@davincho : Thanks. If you've got any other comments or suggestions, please let me know.
Anything specific relating to normalized data that you'd like to have covered?
@markerikson Maybe food for the "Updating normalized data" chapter:
http://stackoverflow.com/questions/39243075/redux-structure-for-image-capturing
I would be thrilled to hear if there is a best practice for this kind of "ownership" updates. I.e. which entity should be responsible for updating the relationship.
In a "slice reducer", is it better to name the state arg state or something closer to what it is? In other words:
function todosReducers(state, action)function todosReducer(todos, action)function todosReducer(todosState, action)Since it's just naming a local variable, totally up to you. I can see arguments both ways. Calling it state would be sticking with a consistent convention. Giving it a specific name could be easier to read for intent and context.
@michaelswe : Read your SO question, and I'm not sure I understand the context enough. Could you sketch out your current store structure for me?
Also, I'm not sure I understand the phrase "which entity should be responsible for updating the relationship". Entities are really just plain data - it's the reducer logic that's responsible for doing the updates.
@markerikson some input from me:
Schema definition.@markerikson Thank you for taking the time for my SO question. "I should have written which reducer should be responsible.."
It almost boils down to a master/detail scenario, but it's a 1:1 relation so I need to update the foreign key of the master after creating the detail. A view is showing the master record and I need to create a detail record and link it to the master.
In the case when images are the "details" some master entity types needs to reference the image in different ways. Some examples:
project.coverImage
profile.avatar
profile.avatarThumbnail
controlpoint.imageBefore
controlpoint.imageAfter
So, after the image is saved to disk I need to instruct a reducer to populate the master record in the correct way. This can be done in many different ways, but I thought that there might exist a best practice for this.
I'm hoping that I managed to explain myself better this time.
Definitely need to address initializing state. Probably best if I just copy Dan's answer in http://stackoverflow.com/questions/33749759/read-stores-initial-state-in-redux-reducer/33791942 .
"Multiple instanced data" issue/discussion roundup: https://github.com/reactjs/redux/issues/1602#issuecomment-208987442
Yay. I have FINALLY managed to crank out "Managing Normalized Data". I also went ahead and copied Dan's answer on "Initializing State" from Stack Overflow into a separate subpage and updated the formatting.
With that, I _think_ I have completed all the major content that I wanted to cover for this effort. The next step is to get some actual eyes on this, and look at usefulness of content, organization, etc.
I have not yet tried to actually build the Gitbook stuff on this branch. I figured I'd wait until I was done content-wise to give that a shot.
@reactjs/redux , @jimbolla , @Aweary , @naw , @phoenixmatrix , @tommikaikkonen , et al: I would greatly appreciate any and all feedback on the stuff I've put together here. I'm definitely figuring that the order of the topics will need to be shuffled a bit. Also, the current link to "Prerequisite Concepts" on the TOC page probably needs to be reformatted better. Beyond that... thoughts? Comments? Suggestions?
Can you make a PR? That will let us comment on stuff line-by-line, rather than on the document as a whole.
@timdorr : Done. See #1930 .
Okay, so we've got the primary content merged in. The TOC file needs to be updated to include the new pages, and I'm wanting to do some renaming as well.
As a side note, the docs seem to have some odd numbering going on. They _should_ be numbered "1, 2, 3", but instead it's "1.1, 1.2, 1.3", etc. Looking at some other Gitbook examples and can't see a real difference in the TOC definition.
This is merged! Yay!
W00t!
Most helpful comment
Was able to crank out first drafts for "Using
combineReducers" and "BeyondcombineReducers" yesterday. Looks like the next couple topics on my list are dealing with normalized data.