-
-
Notifications
You must be signed in to change notification settings - Fork 15.2k
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
Add a "Structuring Reducers" recipe #1784
Comments
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:
|
I could use some advice around using // 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:
|
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 I would think that putting it under a |
@axelboc here are a couple of options:
combinedReducers = combineReducers({
todos: todosReducer
})
rootReducer = function(state, action) {
return Object.assign({}, state, combinedReducers(state, action));
} |
Also see this issue: #1457 In particular, Dan's comment here: #1457 (comment) |
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 |
Two thoughts:
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:
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 I think you could make the argument that We do need to help people understand that It's easy for 3rd party tools just to "assume" everyone is using Maybe it would be helpful to highlight some of these other patterns alongside 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:
And, 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 |
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? 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).. 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 |
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 |
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 |
Immutable data update tools:
|
Was able to crank out first drafts for "Using |
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 ) |
@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: 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
|
Since it's just naming a local variable, totally up to you. I can see arguments both ways. Calling it |
@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:
|
@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 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: #1602 (comment) |
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. |
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! |
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.
WIP page: https://github.com/markerikson/redux/blob/structuring-reducers-page/docs/recipes/StructuringReducers.md
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!)combineReducers
Random related links:
More on reusability for custom components. #1850
Some overlap with the performance/optimization discussions in #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 .
The text was updated successfully, but these errors were encountered: