Closed markerikson closed 6 years ago
A few ideas:
dispatch(actionType, payload)
could reduce need for action creatorscreateReducer({[actionType]: (state, payload) => state}, initState)
Most people I know who use redux heavily, end up moving away from redux-thunk as they find it doesn't scale very well, and ends up leading to action creators that start doing way too much, and rather use something else for managing side-effects like redux-observable or redux-saga.
I can understand the appeal in it, especially when getting started with redux - but I'm not sure if bundling it as part of a 'best practice boilerplate package' would be the best idea.
So, this part is the more critical to me (this will not so much be ideas to solve the problem, but rather constraints that are, at least to me, important):
Redux is not intended to be the "most concise way of doing things", but rather to make data flow obvious and readable
A great thing about Redux is how it's almost more of a design pattern than a framework, and all the code you see (aside for the store and react-redux
) is your own.
Looking at Jumpstate (I did not know about it until now), it's one of the first abstraction layers I see for Redux that looks good. Great even! But at the same time, it brings you only a hair away from other frameworks like MobX, and having multiple frameworks that bring in the same thing to the table isn't super useful. So focusing on what makes Redux different from the rest is important, not just how we can make Redux better in a vacuum (because it lives in the same world as other tools).
Another thing of importance, is that a lot of challenges newcomers have when they hit Redux isn't Redux itself, but JavaScript. Other big frameworks like Angular abstract away JavaScript itself. Writing a Redux reducer is simply applying vanilla javascript "design patterns", which may not be familiar to newcomers, and will make people want to use a black box instead.
Finally, a lot of boilerplate reduction libraries try to add a 1:1:1 relationship between components, reducers and action creators (maybe not all 3, but often 2 of those), which makes newcomers forget they can have multiple reducers handling an action, multiple components using state from multiple reducers, and so on. That's one of the #1 question I answer at work and other places, and it's super useful. So tool to help in that area should not lose this (also, "switch are icky" isn't the best argument in the world).
So IMO, CTAs would involve linking good vanilla JavaScript resources, as well as documenting more of the "why" right along the "getting started" concepts, and keeping things simple. You can build gigantic applications in Redux without learning many new concepts at all. While I'm a redux-observable guy myself, I've seen multi-hundred-thousand lines of code apps using Thunk without issues (and Ive seen tiny apps make a trainwreck with thunks). Introducing very few "core" concepts and showing how they can be applied to tons of concepts helps a lot.
The "all boilerplates must be reduced no matter the cost" is an issue with the software engineering community as a whole these days...thats harder to tackle.
From the very beginning my main concern w/ redux was that either I read or write a code, I had to jump between N files, b/c logic of the single UI part is scattered all over the codebase between actions, action types and several reducers. I really like that I can reach out to any part of the state tree from every place in UI and I can change the different parts of the state in response to a single action (main reasons why I use redux), but the more parts of the state I change in response to a single action, the more my logic is blured. I can't simply read or write what's happened when user did this or that. But what I want is can be described like this:
// meta code
dispatch(ACTION);
onAction = {
ACTION: [
// handler 1: hide spinner here,
// handler 2: change status there,
// handler 3: update entity
],
};
In the end I came up with redux-interactions
+ redux-tree
and I'm pretty happy w/ it so far.
Approach in our current project:
note, would be very different depending on application requirements :) once we add in realtime object updates, perhaps sagas or observables would provide benefit over thunk/promise
service(name, method, ...opts)
which is an action creator with payload being a promise to call our API (i.e. promise middleware picks it up and dispatches x_FULFILLED
, x_PENDING
, and x_REJECTED
). could also overwrite name of action for this.We first use componentDidMount()
to call our API, store data in component state. However, we use our api wrapper for this, which means an action is still sent out (and logged by logger middleware, including meta which is request info), and if we so desire to refactor into using the store for that component all we need to is add a reducer which listens on the action. We start by using local state only, and refactor into redux when that component's state needs to be accessed/modified from another component. That being said, I see the attraction of using redux everywhere, as it provides a paper trail for everything. Given our current team and application timeline, it's just not beneficial for us atm.
I've played with redux-saga a bit, but since our most complicated async flow is login/logout, which works (and code is not terribly complicated), not much of a huge reason to switch (but might be worth it for testing reasons - iterable + mocks is a nice way to test). redux-observable I don't see the immediate benefit unless observables would themselves provide benefit, for example, if you want to double-click events nicely.
We've gotten a bit away from boilerplate by having our own factory functions to return reducers, and having our own higher order reducers on top of custom functionality (e.g. a reducer on top of "paginator" so that content is paginated but can have custom actions to modify an item).
What I think needs to be done is a giant tutorial working up from a basic react app, demonstrate issues that come up with inter-component communication, then introduce redux, then go onwards, demonstrating problems that occur under specific situations, and how they can be helped with redux-x. That is, a guide for when and what to use. There are definitely some blog posts that exist out there with discussion in this direction.
What's also relevant is my summary I came up with for patterns used in gothinkster/react-redux-realworld-example-app
Most people I know who use redux heavily, end up moving away from redux-thunk as they find it doesn't scale very well
I understand what you're saying, but here's my flip-side concern: we're seeing more and more anecdotal evidence that a huge group of JS devs aren't even using arrow functions and other baseline ES(6|2015) features yet, due to lack of understanding, intimidation, etc. Expecting folks who want to get started with Redux, and who could benefit from learning the patterns that Redux introduces, to first learn observables or generators is I think probably asking too much?
Redux-thunk is also slightly complex in the sense that redux middleware in general kind of bends your brain, but it's at least just functions/callbacks, which are easy to pick up if you are writing JS at all. I really like the idea of having a complete package of related tools to get started with, available via a single download, even if it's pitched as "learn-redux" instead of "best-practice-always-redux". Similar to how create-react-app needs tweaking as you learn all the things it set up for you, this could encourage your own tweaking, maybe show how to convert a simple redux-thunk setup to using sagas, etc. as its own form of "ejecting" ...
Just some thoughts.
I understand what you're saying, but here's my flip-side concern: we're seeing more and more anecdotal evidence that a huge group of JS devs aren't even using arrow functions and other baseline ES(6|2015) features yet, due to lack of understanding, intimidation, etc. Expecting folks who want to get started with Redux, and who could benefit from learning the patterns that Redux introduces, to first learn observables or generators is I think probably asking too much?
Bingo! We're in an environment where a LOT of people are new to JS (or they "know" JS, but it's not their specialty, and they start from the deep end). Especially if those folks are experienced software engineers from another ecosystem (java, rails, etc), they will quickly try and apply the concepts they know before learning the ones they don't, and it won't quite work and they get stuck. I don't know what's the best way to convince folks to get a deep understanding of JS before jumping in a functional UX design pattern though.
Note that the Redux docs do have a section about Reducing Boilerplate for those of you reading who are looking for something now and don't want to adopt an entire library on top of Redux.
We should be a little careful about this discussion and realize that this has probably been bikeshedded a lot already. The Redux team has heard, considered and rejected a lot of things that will probably get proposed here. It's quite possible that nothing will come out of this discussion if we only fight about things that have already been discussed.
That being said, I think it's a great idea to talk about ways to make the framework more accessible to everyone (new and experienced).
Anything you propose to reduce boilerplate should be such that it is possible to go back to the stuff below the abstraction whenever needed. It is not fun to adopt an abstraction just to drop it later to go back to the lower level stuff because you needed one extra thing that the authors of the abstraction didn't think of.
Redux learning curve can be steep, but once you grasp concepts, the need for abstraction layers often goes away
I'm wondering, if this is the case, what parts of Redux are we suggesting to improve? What parts would you remove or abstract over that you wouldn't need to immediately add back in?
This is a diagram of the entire react/redux lifecycle I made for a talk at work about a year ago:
There are quite a few parts of this, but I can't imagine Redux working as well without any one of them. Containers are kind of annoying at times, but if you remove them, you deeply couple your view logic with your data layout. If you remove actions, you're basically MVC again which is missing the point of using Redux in the first place. The "view" in that diagram already barely exists because it can be modeled as just a function that subscribes to the store and renders react components.
I don't think there is a lot of boilerplate to remove in the overall framework itself. Maybe you're referring to boilerplate in the individual parts of the framework like in action creators, reducers, or containers. In that case, the tips from the Reducing Boilerplate page mentioned above address most of those things already. We don't need anything on top of that to make it any "better". (Note: It's not that I'm not open to adding something to make things better, I just don't see it yet.)
Maybe this isn't so much a reducing boilerplate problem but a problem of improving Redux education. If the framework is hard to grok for beginners, we may need to improve the Redux docs and make it more accessible. Maybe some of the reducing boilerplate tips need to be advertised more aggressively in the docs.
Reducing the amount of steps required (boilerplate) does not always solve the problem. To stress my point from the very beginning of my post, you don't want to write an abstraction that will get thrown away because you didn't think of every way people would need to use it.
Some good discussion so far. Lemme toss out a few quick examples of common "boilerplate"-related complaints that I see:
dispatch
thing? Why do I have to wrap up all these functions to make them work?"And some specific examples of these types of comments:
And to immediately toss out some responses to those "boilerplate" concerns: of those five categories listed, only "use of dispatch
" is actually required. For the rest:
store.dispatch()
to make anything useful happen - that's a basic design decision of Redux. Binding up action creators makes it possible to pass them around to unconnected child components and still have them dispatch whenever the function is called.So overall, there's almost nothing out of these "boilerplate" concerns that's required. It's a combination of examples from the docs and "good programming practices" like de-duplicating code and separation of concerns.
I think the questions brought up by @markerikson are really fair and I have asked them myself at some point in the past as well.
Redux is in a sense a "low-level" library for data modelling. Like any such low-level library, it exposes a lot of things that you could easily abstract over in order to account for the majority of cases. I think the reason @gaearon didn't originally do that is because he wanted to keep the library as small and flexible as possible. Thanks to that decision, we're able to build a lot of different things on top of Redux without needing to have everything in the Redux core.
Maybe we should consider that the right path might be to develop and stabilize a good library on top of Redux (like Jumpstate?). We start by teaching people that, and then give them an easy path to use Redux directly when they need to.
I don't think Redux needs very much more in its core codebase and I don't see any part of it that needs to be abstracted away permanently. (If you do, let me know :smile:) In my opinion, there isn't a lot to gain by adding or removing things from Redux core.
Improving a library to the point where it's stable and flexible enough for everyone to use is probably a better option. Like you said, not much of the "boilerplate" is actually required, so let's get rid of it in a library on top of Redux instead of modifying Redux itself.
An example of this happening in another community is in the Rust programming language. There is a non-blocking IO library for the Rust programming language called "mio". It focuses on being small and low-level just like Redux does. The thing is, pretty much no one uses it directly because that would be really hard and full of boilerplate. Most everyone uses another library called tokio which builds on mio and makes it extremely usable and ergonomic. This way, anyone who needs the plumbing from mio can use that directly, but anyone who just wants to make something quickly can use tokio.
We should adopt a similar model.
To expand on a couple of @sunjay 's comments:
There was a recent comment in #775 that I think captures things well:
Redux is a generic framework that provides a balance of just enough structure and just enough flexibility. As such, it provides a platform for developers to build customized state management for their use-cases, while being able to reuse things like the graphical debugger or middleware.
So yes, Redux is "just a pattern" in a lot of ways. The core library really is feature-complete - the only real semi-planned changes are things like the proposed enhancer restructuring ( #1702, #2214 ), and possibly making combineReducers
more flexible ( #1768 , #1792 , etc).
Almost two years have passed since Redux was created, and we now have a pretty good idea how people are using it. As one example, @jimbolla collected a list of all known store enhancers in #2214 , and categorized how they work and what they're used for. I'm still absolutely a huge fan of Redux's simplicity and flexibility, but I'd love to see some still-idiomatic abstractions on top of Redux that would simplify general usage and solve problems for people.
One other set of semi-related topics, and something that I have a distinct personal interest in, are the ideas of "encapsulated logic/components" (per @slorber 's "scalable frontend with Elm/Redux" playground ), and "plug-and-play Redux setup" (as seen in experiments like https://github.com/brianneisler/duxtape/issues/1 , https://github.com/jcoreio/redux-features/issues/7 , etc). Global state and app setup is great for some use cases, but not so much for others.
As an interesting related point, @toranb has done some great work creating an Ember wrapper for Redux at https://github.com/ember-redux/ember-redux . The Ember world is really into "convention over configuration", and so ember-redux
sets up a reasonable default store config, including redux-thunk
and such. Some kind of approach like that might be helpful.
So yes, I'm sorta throwing out a whole bunch of different thoughts here, but they are related in various ways. Overall, I want to lower the barriers for learning and using Redux, and enable more advanced use cases across the board.
Redux is a general API, not specialized. This makes it more verbose, while also covering more cases. Its power is in complex software, whereas any new or simple project this is beyond the scope of necessity.
However you can build specializations on top of general APIs. For instance, I use https://github.com/acdlite/redux-actions which reduces boilerplate for common cases. I still use the full power of redux and simply having that interface would not be enough for my needs.
The other problem is with new people who haven't experienced the pain of incredibly complicated applications wondering what's the point. Well, to them they probably shouldn't even use redux until they have experienced that pain, but Dan's egghead series can kick them past this point pretty easily.
https://egghead.io/courses/getting-started-with-redux https://egghead.io/series/building-react-applications-with-idiomatic-redux
Reference: The spectrum of abstraction: https://www.youtube.com/watch?v=mVVNJKv9esE
As mentioned - redux-actions
is quite good at removing some boilerplate, removing constants out of equation by attaching .toString
is a really neat idea I like, however I prefer to write this logic myself than to use redux-actions
myself, but thats personal choice for keeping my code smaller.
Imho there should be a built-in mechanism (subscribe
's callback argument?) for observing dispatched actions stream (i know it can be done in a middleware, but thats quite restricting use case, as it cannot be used outside, i.e. by components or connect
)
I often times teach redux to others and it can be quite daunting for the following reasons
I think redux is a great library that makes total sense in itself and I think it has great abstractions and is super scalable when used correctly. I also appreciate that redux can be used with any UI library but think that in the majority of cases it will be used with react.
Generally I believe a lot of frustration comes from the many moving parts.
Here are some thoughts that I had. All of which can be built on top of redux so it can stay as it is.
render
methodredux action loadUsers
T
Agreed, redux-actions
is a good example of some simple abstractions that remove boilerplate.
Let me ask this: Create-React-App has the react-scripts
package, which has prebuilt Babel and Webpack configs, and comes with a bunch of sensible defaults built-in. What might a corresponding redux-scripts
package look like?
I agree with some of the other (negative) comments about redux-thunk
. I wouldn't recommend it to beginners (I probably wouldn't recommend it at all anymore):
dispatch
from props and pass it in works out to be only a small part of the code.We used it pretty heavily in a React-Native application, and it was mostly useful, but we had trouble composing actions together (think "load profile after successful login"), due to redux-thunk action creators effectively returning void
(ie. having nothing such as a promise to use for sequencing the next action).
It tends to encourage people to think that "the only way to do anything is to dispatch
immediately from the React event handler/callback". And since we were using connect
from react-redux
and leaning heavily on mapDispatchToProps
, we tended to forget that our event handlers could just be plain functions.
(note that the application above was mostly written early last year, and we didn't really keep up with what was happening in the greater Redux ecosystem, so it may be out of date).
@bodhi : without going off on too much of a tangent, I'd disagree with some of those conclusions. redux-thunk
is 11 lines long, and there's some nice explanations out there like "What is a Thunk?". Thunks are composable to some extent, and I think it's generally easier to tell someone "write these couple lines of function declarations, then make your AJAX calls and such here". I have a post that discusses some of the various tradeoffs of thunks (and to some extent sagas) at Idiomatic Redux: Thoughts on Thunks, Sagas, Abstraction, and Reusability.
I do agree that there would need to be some "blessed" or "built-in" way of handling side effects in some hypothetical abstraction layer. Sagas and observables are both great, if you understand how to use them. I just don't know if they'd be appropriate, given that a potential goal here is to provide an ease-in path to learning and using Redux.
I find that using redux-thunk
is not just about doing something asynchronously, but also doing something that depends on the current state. Very clean, simple interface, and encourages the use of action creators.
Bodhi: Perhaps a bit off-topic, but just thought perhaps it's worth mentioning that dispatch doesn't return void, it returns the result of your inner function. So if you for instance return a promise, you can easily chain actions together. See section on composition in the readme: https://github.com/gaearon/redux-thunk/blob/master/README.md
Other than that I would just like to say thanks to the community for having this open discussion, very interesting to hear everyone's thoughts and ideas. Personally sticking to thunks so far but trying to clearly separate "what happened" from "how should the state change". I see quite a few instances in our code where we make that mistake (using actions to tell the reducers what to do, instead of reducers just reacting to what happened), so it would be nice to see some library that somehow made that harder to do (not sold on sagas yet).
On 19 Mar 2017, at 23:48, Bodhi notifications@github.com wrote:
I agree with some of the other (negative) comments about redux-thunk. I wouldn't recommend it to beginners (I probably wouldn't recommend it at all anymore):
It seems like magic to start. It doesn't really take away that much boiler-plate. Given a more complex async process, having to get dispatch from props and pass it in works out to be only a small part of the code. It results in action creators that you cannot compose together into larger workflows. A last point that I can't articulate very well about it making the redux style into more of a framework than a library: you rely on the framework to call your code, rather than you calling into the library. We used it pretty heavily in a React-Native application, and it was mostly useful, but we had trouble composing actions together (think "load profile after successful login"), due to redux-thunk action creators effectively returning void (ie. having nothing such as a promise to use for sequencing the next action).
It tends to encourage people to think that "the only way to do anything is to dispatch immediately from the React event handler/callback". And since we were using connect from react-redux and leaning heavily on mapDispatchToProps, we tended to forget that our event handlers could just be plain functions.
(note that the application above was mostly written early last year, and we didn't really keep up with what was happening in the greater Redux ecosystem, so it may be out of date).
— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub, or mute the thread.
(sorry to continue the tangent, but...)
it's worth mentioning that dispatch doesn't return void, it returns the result of your inner function.
Huh, great! Guess I just never looked too closely at the documentation before trying to find another way to solve our problems.
The one most valuable thing that made Redux so much easier for me is treating actions as events and not commands. This completely eliminates the struggle to decide how to design actions and what to put in them.
Components don't have to know how user interaction should affect the state, they only tell the outside world what did happen inside them, e.g. "button X clicked". When using actions as commands, you effectively introduce a two-way binding between component and state.
Reducers are not bound to components, they represent application state and not component state, and they are the only party that actually knows how the state changes. There's no cognitive barrier in handling single action in multiple reducers because an action is not a statement "what to do with the state".
This also removes any case for dispatching multiple actions synchronously.
Any case for dispatching single action from multiple places also goes away. You can keep action creators near the component which makes it easy to track down the component that dispatched action.
By looking at action log in DevTools you can see a complete history of what happened inside your app and it's easy to see the concrete state slice affected by the action. The history remains the shortest possible.
This pull-based approach makes the data flow truly one-way and stacks naturally with side-effect management libraries like redux-saga
or redux-observable
.
@aikoven although I totally agree with this (there was definitely a lot of discussion in the past comparing redux to event sourcing), what do you do with things like FETCH_USER
, which is very common to see (and something that needs to be done). That seems more "command-like" then "event-like".
@blocka There's always some event that results in a network request. The request itself can be wrapped in FETCH_USER_STARTED
and FETCH_USER_DONE
/ FETCH_USER_FAILED
actions, which is how request result ends up in the state.
👍 for redux-actions
What I also like about it apart from just reducing boilerplate is, it just feels like a very natural extension of redux. For something like jumpstate
I feel the implementation is going a bit farther away, and now I'll have to kind of learn two things instead of one.
So my point is, any plugins we add to reduce boiler plate should feel very much like working with redux albeit one with a less verbose API.
A lot of the boilerplate is the same boilerplate that you have in any environment. When you come across duplication, we usually solve it with a custom abstraction. These abstractions are, very often, not useful outside of your current project, and a "silver bullet" just can't exist. Every project's requirements are different.
Same thing with redux. If you fine that you need a FETCH_X_STARTED
action, and a fetchX()
action creator, and reducer to handle that, etc., feel free to make your own abstraction up!
Blaiming redux for boilerplate is like copying and pasting all your code, and then blaming javascript for having too much boilerplate.
I find that using redux-thunk is not just about doing something asynchronously, but also doing something that depends on the current state. Very clean, simple interface, and encourages the use of action creators.
Using redux-thunk for synchronous code isn't a great idea and should be mentioned in docs. If you compose many functions together you aren't certain that dispatch flow is not order dependent so it's harder to reason and test.
It seems better to me to have pure actions creators and simply use actions batching for more complex state modifications. This way you have only pure functions to test.
I recommend this blog post to get better understanding: http://blog.isquaredsoftware.com/2017/01/idiomatic-redux-thoughts-on-thunks-sagas-abstraction-and-reusability/
@Machiaweliczny thanks for the article. I guess the question to me is more about where you end up putting your business logic (as discussed in https://medium.com/@jeffbski/where-do-i-put-my-business-logic-in-a-react-redux-application-9253ef91ce1#.3ce3hk7y0). You are not saying that your dispatch flow (thunk, epic, saga, whatever), should never have access to the current application state, are you?
@dfbaskin
Keep in mind that Redux isn't domain driven development. "Business logic" is a much fuzzy concern here, and it's more "what concept's function signature best fit the logic I'm trying to implement". Your "business logic" is spread out across selectors, reducers, action creators and "orchestrators" (do we have a better word for this yet ?) depending on what you need.
The reducer, being (state, action) => state
is where "logic that depends on state" generally should be. And sure enough, in things like the Elm Architecture, all such logic is in the "update" function (the equivalent to our reducer).
Unfortunately in Redux, the reducer can only ever handle synchronous logic. So asynchronous "business logic" that depends on state either needs to get it from the component via dispatch (if the component has that info, which is not always), or through your orchestrator concept of your choice (thunk, sagas, epic), which have access to state for that.
If it wasn't for the reducer's limitation, there would be no need for it. You actually see it when people use redux-saga or redux-observable: their actions/action creators usually become near-trivial (in most cases, plus or minus some normalization or formatting), and the saga/epic are almost "alternate reducers", on that you now have another thing that has access to action and state and return a "thing", and the actual reducer's complexity is diminished as a result. Reducers and orchestrators are very closely related (sometimes too closely, and it's not great to have 2 constructs that are interchangeable half of the time...but it's what we have, might as well enjoy it)
@Machiaweliczny : as author of that "Thoughts on Thunks" post, I can assure you that "avoid using thunks for synchronous work" is the opposite of what I was trying to say :)
More good discussion, but we're starting to drift a bit far off topic. (Is that a bikeshed I see over there? :) )
Let me point back to the original questions:
What concrete steps can we implement to address these?
In the realm of uncontroversial, actionable, short term steps, I would say:
1) Make the store easier to configure (maybe with a default configuration? I don't know if this is still the case, but Elm used to have a "starter app" type, and a regular app type. The starter app was used for simple apps and in training material to cover the 90% case when people are just learning. To this day, I'll admit I cannot configure the Redux store with basic middlewares (devtools, thunks) without looking up the doc for the signatures. I don't think many people would argue that newcomers need to know how to configure a store's middlewares to learn the Redux core concepts.
2) This may already be the case, make sure adding Redux with the above "starter store" to create-react-app is as easy as possible, so a newcomer can have an up and running Redux app in 2 minutes.
I think this would get us very, very far.
Edit: by starter app, i don't mean an app boilerplate, but rather a "createStore" that can't be configured but has reasonable defaults that people can just import and be done with it. Then they can just move on to the "real" createStore when they outgrow it.
That sounds like a pretty fantastic concept right there.
On Mon, Mar 20, 2017 at 10:02 AM Francois Ward notifications@github.com wrote:
In the realm of uncontroversial, actionable, short term steps, I would say:
1.
Make the store easier to configure (maybe with a default configuration? I don't know if this is still the case, but Elm used to have a "starter app" type, and a regular app type. The starter app was used for simple apps and in training material to cover the 90% case when people are just learning. To this day, I'll admit I cannot configure the Redux store with basic middlewares (devtools, thunks) without looking up the doc for the signatures. I don't think many people would argue that newcomers need to know how to configure a store's middlewares to learn the Redux core concepts. 2.
This may already be the case, make sure adding Redux with the above "starter store" to create-react-app is as easy as possible, so a newcomer can have an up and running Redux app in 2 minutes.
I think this would get us very, very far.
— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/reactjs/redux/issues/2295#issuecomment-287806663, or mute the thread https://github.com/notifications/unsubscribe-auth/AFUmCQYkUwjkCugevDBWC8TOu0HhWJTnks5rnqMWgaJpZM4MhnVF .
Our modules make use of combineReducers
so that each state slice in a module has a dedicated reducer for it (I believe this approach is outlined in the Structuring Reducers section of the docs a.k.a. "slice reducers"). This makes things easier to reason about because the switch statements are much smaller. It also allowed common reducers to emerge across our code base. The simplest reducers were identical across modules except for their action types, so we were able to reduce boilerplate by having helper functions (reducer factories?) that makes these reducers for us by action type:
const { makeIndicator, makePayloadAssignor } from '../shared/reducers';
const searchModule = combineReducers({
searching: makeIndicator(c.SEARCH),
results: makePayloadAssignor(c.SEARCH_RESPONSE, [])
});
Perhaps identifying more common slice reducers like this would be a good way to alleviate the boilerplate concerns, where they could serve as primitive building blocks for our reducers.
This is basic javascript refactoring. As I said before..."javascript" has a lot of "boilerplate", until you apply things like abstraction to your code.
Redux is just javascript. There is nothing magical about it.
On Mon, Mar 20, 2017 at 12:44 PM, Markus Coetzee notifications@github.com wrote:
Our modules make use of combineReducers so that each state slice in a module has a dedicated reducer for it (I believe this approach is outlined in the Structuring Reducers section of the docs a.k.a. "slice reducers"). This makes things easier to reason about because the switch statements are much smaller. It also allowed common reducers to emerge across our code base. The simplest reducers were identical across modules except for their action types, so we were able to reduce boilerplate by having helper functions (reducer factories?) that makes these reducers for us by action type:
const { makeIndicator, makePayloadAssignor } from '../shared/reducers'; const searchModule = combineReducers({ searching: makeIndicator(c.SEARCH), results: makePayloadAssignor(c.SEARCH_RESPONSE, []) });
Perhaps identifying more common slice reducers like this would be a good way to alleviate the boilerplate concerns, where they could serve as primitive building blocks for our reducers.
— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/reactjs/redux/issues/2295#issuecomment-287820595, or mute the thread https://github.com/notifications/unsubscribe-auth/AACourLpL5--NJiRzrWmKFJ31cl5DXJrks5rnqzxgaJpZM4MhnVF .
I agree with most commenters that said that is better to explain common redux patterns than develop some boilerplate reducer. My teammates and I started to work with redux 1.5 years ago and were mostly confused about simplest things: that redux is mostly event-sourcing, that the same action can be handled by several reducers, that action can contain whole business enitity not only its id, that reducers are just functions and you are free to do anything you want with them - compose, generate, split, etc. We made common mistakes - used actions as remote commands and wondered why just not to use classic method calling; created huge middlewares and avoided redux-saga because "generators is too complicated" (though redux-saga is superb!), made long files with huge switches. This is of course because of mediocre programming skills but also because of lack of structured information. Today documention is much better, thanks a lot to the maintainers!
This is a very valuable topic on Redux as a library.
This is basic javascript refactoring. As I said before..."javascript" has a lot of "boilerplate", until you apply things like abstraction to your code. Redux is just javascript. There is nothing magical about it.
Perfect, @blocka! I think this message needs to be spread.
Redux is feeling unusual and boilerplate-ish to people because of its minimal approach. Minimal feels weird for a "framework" in general (minimal code, minimal principles).
Some people are used to writing "framework-y" code rather than writing JavaScript itself. When developers use a framework, built-in way do to things are implicitly expected. Sad as hell but true.
I believe Redux is getting easier to get started with than it ever was, but we still need to make it clear for people to not to expected Redux to do everything. @gaearon clearly says this all the time, but unfortunately people are used to frameworks doing everything. Why should they get motivated to learn something that "doesn't do much"? Angular does a lot more than Redux. Why Redux? The answer is probably evident for all of us in this issue. But is it clear to beginners?
I wrote a short article about it yesterday: Don't blame it on React or Redux. Of course I can be wrong on all this.
There is precedent for having abstraction layers on top of an advanced library. Just recently, Tensorflow brought in Keras as an abstraction on top of the core API: http://www.fast.ai/2017/01/03/keras/.
Using TensorFlow makes me feel like I’m not smart enough to use TensorFlow; whereas using Keras makes me feel like neural networks are easier than I realized. This is because TensorFlow’s API is verbose and confusing, and because Keras has the most thoughtfully designed, expressive API I’ve ever experienced. I was too embarrassed to publicly criticize TensorFlow after my first few frustrating interactions with it. It felt so clunky and unnatural, but surely this was my failing.
I feel like that is a similar experience for most first timers coming into Redux. Replace Tensorflow
with Redux
and Keras
with Jumpstate
. Redux is very powerful, but the majority of users probably don't need all the control that is available. More than likely, they're coming from Angular or being taught React + Redux at a bootcamp or from watching various tutorials. While Redux doesn't need to be dumbed down for new users, it also isn't an anti-pattern to provide an easier abstraction that can probably cover 80% of potential use cases.
Y'all, if redux was just fine the way it is, we wouldn't be having this conversation. Regardless of whether redux is useful or structurally sound, people still feel "bad" when they're first learning it. These feelings are valid.
The people commenting in this thread are all redux experts -- of course we're happy with redux; we have internalized its philosophy and we feel comfortable making our own abstractions for managing "boilerplate." We are not the audience this thread is intending to serve. We need to have some empathy for users who are coming to redux for the first time, maybe even coming to functional programming for the first time.
For example: here's a complaint I frequently hear about redux (that I dont think has been mentioned upthread): its not enough to just use redux, you also "need" to use react-redux, redux-thunk, redux-actions, etc.
Do you literally need to use all of those other packages to use redux? No. Will most redux users end up using some or all of those additional packages? Yes.
Does the number of packages in package.json
matter? No, not really.
Does the number of packages in package.json
affect the way people feel? Absolutely.
Now, I think its fair to believe that redux itself should remain as it is, and another package (create-redux-app
or whatever) should handle assembling them together. But we have a real complexity problem on our hands and its not enough to tell the users to RTFM.
Regardless of whether redux is useful or structurally sound, people still feel "bad" when they're first learning it. These feelings are valid
Absolutely. But not all people feel that way. Some do. Keep in mind that you'll never hear from people who didn't have problems. You only see the ones who ask you questions, or hit stackoverflow/reactiflux. Of those who never do, some also need help but don't know how to ask for it...and some are doing fine, too, and you don't want to make it worse for them...else they'll be the one coming to forums instead and it will be a net zero gain.
Redux didn't get popular for no reason. A heck of a lot of people thought it felt darn good and recommended it to others :)
Now, I think its fair to believe that redux itself should remain as it is, and another package (create-redux-app or whatever) should handle assembling them together.
I liked this idea. It'd be possible to argue that existing boilerplates already solve it, but React also had a lot of boilerplates at the time that people were complaining about being overwhelmed with tools when trying to get into React. create-react-app
was a great answer to all this, because it has eliminated a lot of steps for beginners to get into React itself already.
But it's still boilerplate under the hood. So, we still need to encourage people to buy into the ecoystem and make it clear that Redux itself doesn't do much by design. It's not possible to run away from the ecosystem if you want to use React or Redux.
I don't believe that including packages such as redux-thunk
in Redux core is the way to go here though.
Just for clarity, the kind of ideas I'm tossing out aren't about actually including things directly in the core. They're about hypothetically creating some kind of "pre-built configuration" or "ease-of-use abstraction layer" that would be a separate package or CLI tool or something, more along the lines of create-react-app
.
I figure I'd better chime in as the author of Jumpstate/Jumpsuit.
TL;DR: Redux is hard to learn and hard to configure / integrate with because it is so low-level. The community could benefit from a standardized higher-level redux api aimed at the 90% use case to ease the learning curve and even operate as a solution for simpler projects. Jumpstate is trying to solve this, but is not a long term solution. We need to start proposing possible ideas (real or meta code) on how this api could look.
Redux is very difficult to learn out of the gate and, very often, just as difficult to integrate with new concepts like async logic, side-effects, custom middleware, etc. I experienced this myself only a little more than a year ago when I was very new to the react ecosystem.
Only a few days into learning react I was immediately recommended to learn redux. This recommendation came from both very experienced and novice devs who had done (or were doing) the same. It was being hailed as the de facto solution for anything that extended beyond setState (let's keep setState gate out of this though 😉 ). All of this for good reason, because redux is amazing.
I inevitably hit the learning curve and when seeking help immediately realized that the marketing and documented approach for redux took on a tone of "Redux is hard, the concepts are low-level, the api is locked in, and you just need to power through it."
So this is what I did along with all of my peers. We powered through it because it was what we were educated to do. Along this journey though, I dared to create an abstraction layer very early on that would temporarily and deliberately hide some of the complexity and advanced features that redux had to offer.
I didn't need custom action creators, didn't want to worry about mapping actions and dispatchers to components, and like many others, opted for a less verbose reducer layout in place of a switch statement.
It worked fantastically. So well, in fact, that it expedited the process of learning redux for the rest of my peers and other devs who were similarly struggling with the learning curve.
We designed it in such a way that advanced or "idiomatic" redux could still be used when we needed it, but truly designed it for our 90% use case.
By no means am I saying that jumpstate has covers all the bases, in fact, there are still sooo many things that need to change for jumpstate to behave "idiomatically" (customizable action creators, removing global action imports... there's a lot), but it has definitely helped many people learn the basics and gave them a path to understand how redux works on a very simple level. All the time I receive message in the Jumpstate slack org that jumpstate helped someone learn redux.
Whether those people are still using jumpstate today or not doesn't matter that much (but many people are still using it production today). What matters was the experience they had, and how quickly they were able to become productive in the redux ecosystem.
Some more ideas that come to mind for this discussion:
How much of this could be solved with improved docs in some way?
A lot. When it comes to the Redux documentation, I feel to many degrees it is worse than AngularJS 1's docs because like AngularJS's, it is unnecessarily verbose but unlike AngularJS's, it fails the "show, don't tell" principle.
Redux's docs prefer code snippets rather than actual demonstration of the code which means there's too much "here's a set of code; we won't tell you how it connects but trust us that it works" moments. The lack of ability to run examples within a browser means that the user is forced to trust their own instincts that what they are doing locally works and if something doesn't work, they are off on their own. Even the simple Todo List app I feel it's hardly the best "hello world" example of Redux - it could be made much simpler.
Diagrams would certainly help here. For example, I like @sunjay's one; one that might even be better is to design one for the the Todo List example itself - how are the files connected, etc. from a high-level view. Images are almost always better for conveying long text and can help reduce the verbosity of the docs. The images would also help beginners keep their focus and better understand Redux as a whole.
My two cents: please don't change core. Its brilliance is in its simplicity. At this point, adding something to it would likely take away from the whole.
The user-friendliness and further abstraction can happen in userland. I don't feel like there needs to be any official or idiomatic way to use it either. It's a predictable state container, that's it!
It can stand on its own, let's it be ❤️
That said, sure, package it up however you want by creating more opinonated libs on top of it. That's what I do too, but let's not change core, pretty please.
@HenrikJoreteg, have no fear! I'm almost certain that nothing we discuss here is ever going to change the core. The core is feature complete and stands on its own. :)
A lot. When it comes to the Redux documentation, I feel to many degrees it is worse than AngularJS 1's docs because like AngularJS's, it is unnecessarily verbose but unlike AngularJS's, it fails the "show, don't tell" principle.
This is very true. One thing when Redux was new is that doc was sparse, but the basics were there. Many people adopted it because it was "so simple". But online resources are now trying to show "real world techniques", which often are just "my favorite middleware" and "my favorite boilerplate reduction tooling". People get overwhelmed.
There's something to be said about "less is more"
@Phoenixmatrix , @Sylphony : so, what would a potential docs revamp look like?
Personally, to combine it with my suggestions above of having a "starter-store", I'd split it in two (not 2 different sites/documents, but 2 different sections).
The "Redux architecture", which would have a lot of cute, simple drawings, explains actions and reducers, introduces react-redux and maybe thunks, uses the "starter store" to limit any kind of upfront configuration, and shows how easy it can be to make a simple app that does a few ajax requests and render stuff on screen.
Everything else can go in an "explore" section with the more advanced concepts. Even then, I'd probably rip out a lot of stuff. Someone pointed out to me today that there's a section on having multiple stores in an app. That's good to know, but the uses for it are really advanced. I don't think any newcomer needs to stumble on it without looking.
Better docs are always a great idea, but I don't think that solves the core issue here. Abstraction libraries are being written and adopted because of the boilerplate and verbosity of the Redux api. Those same libraries are fragmenting the Redux community because they are obviously needed at some level or another. It is frustrating when those same authors who are actively trying to contribute to the ecosystem are put down and dismissed publicly because they "don't understand Redux". If the community can instead create a simpler abstraction, not part of React core, but "blessed" to some degree, then everybody wins. Hardcore users keep using Redux as is, other people can use an abstraction with an easier/less verbose api.
@derekperkins How do we come to consensus about a "blessed" library? How do we decide what goes in it and who is going to work on it?
I'm all for something like this, but how do we avoid just creating another library that only some people use and thus fragmenting the ecosystem even further.
Relevant xkcd: https://xkcd.com/927/
The number one complaint I see about Redux is that there's "too much boilerplate". I also frequently see complaints that there's too much to learn, too many other addons that are needed to do anything useful, and too many aspects where Redux doesn't have any opinion and therefore doesn't offer any kind of built-in guidance.
I just had a discussion with @tannerlinsley regarding several of these aspects. We discussed the Jumpstate library, which is an abstraction layer around Redux, and how it was intended to ease the learning curve, as well as various philosophical opinions about what constitutes "good" Redux usage.
Out of that, we came up with several questions that I would like to bring up for wider discussion:
Key points
Boilerplate / Verbosity
Abstractions and Learning
Problems
Potential Solutions
I would like to invite the community to offer up complaints, pain points, and concerns about using Redux, and hopefully also provide suggestions and ideas for solving those problems as well.