-
-
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
Request for Discussion: Redux "boilerplate", learning curve, abstraction, and opinionatedness #2295
Comments
A few ideas:
|
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):
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 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 |
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
We first use 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 |
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. |
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.
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:
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
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 😄) 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:
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 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 brianneisler/duxtape#1 , jcoreio/redux-features#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 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 Reference: The spectrum of abstraction: https://www.youtube.com/watch?v=mVVNJKv9esE |
As mentioned - Imho there should be a built-in mechanism ( |
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.
T |
Agreed, Let me ask this: Create-React-App has the |
I agree with some of the other (negative) comments about
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 It tends to encourage people to think that "the only way to do anything is to (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. 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 |
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 ***@***.***> 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...)
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 |
@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 |
@blocka There's always some event that results in a network request. The request itself can be wrapped in |
👍 for 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 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 Blaiming redux for boilerplate is like copying and pasting all your code, and then blaming javascript for having too much boilerplate. |
Using redux-thunk for synchronous code isn't a great idea and should be mentioned in docs. 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? |
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 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) |
Its worth differentiating between two different sets of ideas going on in this thread:
I agree with @markerikson that redux core should not become a framework in itself, and I understand why one would want to keep redux-thunk and react-redux in separate packages. However, I believe there is both room and precedent for new "helper functions" in redux core. Redux has a fairly small API surface area:
None of these are essential to the functionality of Redux yet all of them are essential to its usability. None of these functions limit the inherent flexibility of Redux, but by reifying common patterns into callable functions they make choices less daunting. And each of these helper functions raises further questions:
Even if the answers to all of these are negative, I think we do ourselves and the community a disservice by refusing to even consider their premises. |
@modernserf : First, thanks for your comments, as well as your other recent thoughts on Redux. They are all extremely valuable and informative, and should probably be required reading for anyone else in this thread. So, I'll link them here:
It's very interesting that you should bring those up, because of the history involved (which I just spent a lot of time researching for my post The Tao of Redux, Part 1 - Implementation and Intent. In particular, You're right that those utilities do indeed codify certain usage patterns. That said, those utilities are included because they codify the intended usage patterns: composition of slice reducers, a pipeline of middleware for async behavior and other centralized behavior, and bound action creators to simplify the process of dispatching actions from components (particularly passing functions around to child components). Somewhat similarly, while Reselect isn't in the core, Dan did explicitly encourage its creation. So yes, anyone "could" have written those, but by building them in, we've pushed people to use those specific tools in specific ways. Definitely agree on that. Per your last three questions:
As I documented in that "Tao of Redux" post, the stated goals were to keep the Redux core API as minimal as possible, and encourage an ecosystem on top of it. In addition, early in Redux's development, Andrew made a comment about the intent to "bless" certain plugins:
Earlier up-thread, I had proposed some kind of "easy Redux setup" abstraction lib, or something like that. Would something like that, or at least that list of "blessed" addons, be sufficient along the lines that you're thinking? (And, actually, you did too in the first comment of this thread.) If not, any additional proposals or ideas? |
This is what I would do as well, though I'd probably take it a step further: I would restructure the project into a monorepo, where the current That sort of satisfies the constraint of not modifying redux core. Also, the extended version would be a strict superset of -- and therefore backwards compatible with -- the core. The newly features would be purely opt-in and have no special access to private state. As far as "bloat" goes -- each of these libraries is small to begin with, and they're written in such a way that it would be trivial to tree-shake them.
I think this should tell you something. The monorepo structure I'm suggesting is basically the structure used by Lodash. Now -- Lodash is an interesting library to model after: its an enormous collection of functions, many of which are trivial to implement yourself. Nearly every function in lodash could be more flexible as a "recipe" -- And every lodash function is also available in its own a la carte library. There's no functional benefit to But the experience of using Lodash is fundamentally different than using recipes or nanolibraries. You know that if you're doing something with an array and you get a feeling like you're repeating yourself, there's probably a Lodash function to do it. You don't need to look up a recipe, you don't need to install anything -- chances are you don't even need to add another import line to the file. Its removing only the tiniest amount of friction but our days are filled with moments like this and it adds up. |
I hope you don't mind me adding my own 2 cents, from a different perspective. The CQRS, Event Sourcing, and ultimately Domain Driven Design schools of thought are the greater ancestors which bore us Flux and Redux. While these influences are occasionally cited, Redux and its descendant tooling took most of their API terminology from Flux and Flux implementations which were popular at the time. This is fine, but I think we're missing some opportunity to profit from existing exposure developers may have had to those ideas. To give two examples of the difference: In most Event Sourcing discussion I've seen, Redux Actions would be referred to as Events. Redux ActionCreators would be referred to as Commands. I think there's some added clarity and semantic that comes for free with this terminology too. Events are loggable, serializable data. Events describe what happened in the past tense. Events can be replayed. Events cannot be un-recorded. Commands have an imperative mood. Commands trigger Events. I suspect that Event Sourcing as an architectural patterns will gain in popularity over the next decade, and I think that Redux could gain some usability, clarity and ease of use wins by aligning the API and discussion more closely with the broader community. |
I think this discussion was already beaten to death: #351 |
Oh, I wasn't aware of that, thanks! 👍 It looks like #351 was closed because at the time there was no action that could be taken. If we are at a juncture where we are reconsidering API decisions and the language we use, this seems like an appropriate time to resurface the idea, to me. I can be more concrete. Here's my idea of what an API inspired by Event Sourcing idioms might look like: Concepts
APIimport { createEventStream, createProjection } from 'redux';
// Initialize the event stream separately from the store. This becomes the one
// true source of truth for your application.
const eventStream = createEventStream({
// Commands are the only thing that we want to couple to the eventStream. The
// set of events which may end up in an eventStream should be easy to predict.
//
// A definition like this supports static analysis inference well for
// consumers that can leverage it.
increment: () => ({ type: 'INCREMENT' }),
decrement: () => ({ type: 'DECREMENT' }),
});
// Multiple stores with disjoint or overlapping data can be used to consume the
// same event stream.
const store = createProjection(eventStream, reducer, init);
const adminStore = createProjection(eventStream, adminReducer, init);
// We don't need a jargon term ("Middleware"), or a dedicated hook to handle
// async anymore. We just register more subscribers to the eventStream.
eventStream.subscribe(myCustomMiddleWare);
eventStream.subscribe(sendEventsToAnalytics);
eventStream.subscribe(logEventsForPlayback);
// Calls to commands can be wrapped with React Providers or container components
// in the same way that Redux currently does. They can also be called directly.
eventStream.increment(); |
@ajhyndman : while I do appreciate the suggestion and discussion, that bikeshed has totally been painted and the horse has fled the barn (to completely mix metaphors). Dan opted to use Flux terminology rather than CQRS/ES terms, and there are no plans to change the core APIs or the terms used for Redux concepts. (Also, while I have zero stats to substantiate this, I would guess that at this point there are more people familiar with Redux's use of "actions" and "action creators" than with CQRS/ES terminology, at least within the JS community.) |
I definitely get the impression that this conversation has been had before, and there's a strong desire not to re-open it. 😄 I won't press too much harder here. (I'd be interested in reading or continuing this conversation elsewhere, if there's a better touchpoint.) Of course, you're also right that there is an entrenchment factor, and changing the entire API surface and terminology would be costly at this point in time. I would still argue there's opportunity for the Event Sourcing and Redux communities to learn from each other.
Even without borrowing the terminology or changing the API, I think there are wins we can find. For instance, I have had success introducing people to Redux by describing a store as "a state container in which currentState is a "pure function of" (it is derived from, or reduced from) an append-only list (or a stream) of Actions". Looking further into the future, I think it's entirely possible to implement and abstract a redux-like server infrastructure (see: Apache Samza, and some of the other work from LinkedIn's engineering team). If we aspire to that, then it should also be possible to use similar abstractions for client and server state management! If we can achieve that, why not an isomorphic, persistent Redux store? Any concepts that the JavaScript and Infrastructure communities are able to come together on, or easily map between, the more quickly I expect these opportunities to become apparent and the more expressive the abstractions. I guess I'm just getting a little excited about some of these ideas! I hope you'll forgive me for the word dump. Side note: It seems to be possible to create an event-stream-like wrapper API for Redux that implements the surface I suggested above! |
@ajhyndman I think the wrapper is the right way to go with the your idea there 👍 Related to the samza and linkedin engineering work you mentioned, If others have not read/watched the wonderful talk Turning the database inside-out with Apache Samza then please find an hour to do so sometime! I think I saw dan mention it in a readme or tweet at some point, This talk changed how see databases and state management forever and is a very close idealogical cousin of redux. |
That video is awesome! It's also actually the second item in the list of Thanks on the |
Hey everyone! Without being aware of the existence of this thread, I have developed a library that's in a way a direct answer to @markerikson's original questions and knocks off most things from @gaearon's list. The library is called Kea: It is an abstraction over redux, redux-saga and reselect. Except for the glue connecting them together, Kea doesn't pretend to create anything new and exposes raw redux action creators and reducers, raw redux-saga sagas and raw reselect selectors as needed. It doesn't invent new concept. Kea supports many different modes of operation: You can use it standalone, connected to React components, inlined above React components or even dynamically connected to React components where the input to the selectors depends on the props of the React component. I personally use it with two large applications: one marketplace for private lessons and in one huge fleet tracking software. Plus I know of people who have built their own apps with it. Because it was so useful for us, I spent considerable time cleaning it up and writing documentation. For me this library has always greatly reduced boilerplate, while remaining true to the core of redux itself. Anyway, enough talk, here's some code. This is an example of what I call "inline kea" - where the logic is attached to your component directly. This mode is great for when you're just getting started or as an alternative to React's import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { kea } from 'kea'
@kea({
actions: () => ({
increment: (amount) => ({ amount }),
decrement: (amount) => ({ amount })
}),
reducers: ({ actions }) => ({
counter: [0, PropTypes.number, {
[actions.increment]: (state, payload) => state + payload.amount,
[actions.decrement]: (state, payload) => state - payload.amount
}]
}),
selectors: ({ selectors }) => ({
doubleCounter: [
() => [selectors.counter],
(counter) => counter * 2,
PropTypes.number
]
})
})
export default class Counter extends Component {
render () {
const { counter, doubleCounter } = this.props
const { increment, decrement } = this.actions
return (
<div className='kea-counter'>
Count: {counter}<br />
Doublecount: {doubleCounter}<br />
<button onClick={() => increment(1)}>Increment</button>
<button onClick={() => decrement(1)}>Decrement</button>
</div>
)
}
} In case your app grows and more places need access to the // counter-logic.js
import PropTypes from 'prop-types'
import { kea } from 'kea'
export default kea({
actions: () => ({
increment: (amount) => ({ amount }),
decrement: (amount) => ({ amount })
}),
reducers: ({ actions }) => ({
counter: [0, PropTypes.number, {
[actions.increment]: (state, payload) => state + payload.amount,
[actions.decrement]: (state, payload) => state - payload.amount
}]
}),
selectors: ({ selectors }) => ({
doubleCounter: [
() => [selectors.counter],
(counter) => counter * 2,
PropTypes.number
]
})
}) // index.js
import React, { Component } from 'react'
import { connect } from 'kea'
import counterLogic from './counter-logic'
@connect({
actions: [
counterLogic, [
'increment',
'decrement'
]
],
props: [
counterLogic, [
'counter',
'doubleCounter'
]
]
})
export default class Counter extends Component {
render () {
const { counter, doubleCounter } = this.props
const { increment, decrement } = this.actions
return (
<div className='kea-counter'>
Count: {counter}<br />
Doublecount: {doubleCounter}<br />
<button onClick={() => increment(1)}>Increment</button>
<button onClick={() => decrement(1)}>Decrement</button>
</div>
)
}
} Please try it out and read the docs to see more about side effects through redux-saga and other features you can use. The feedback for Kea has been overwhelmingly positive until now, quoting from an issue: "More people should use KeaJS as its making redux world a better place! 👍" :) Thank you for your time and for reading this far! :) |
Looks nice! not really liking the magic strings though |
Did this discussion move forward on another place? Of is Kea the most correct outcome which is generally accepted? |
@lucfranken : no, the discussion fizzled out. If you're looking for a higher-level abstraction over Redux, then Kea is probably a good option. If you're looking for a "Redux starter pack" that simplifies the store setup process, you may have to look around some. I know I've seen some things like that out in the wild, but we don't have any official lib like that right now. |
Throwing my hat in the ring here. This is how I address this issue: https://github.com/HenrikJoreteg/redux-bundler I personally think it's a big simplification and reduction of boilerplate. It's how I've been building everything I build recently. |
Guess it would help : |
Hey, in my company we just open sourced a library which manages the network layer and removes a lot of boilerplate from Redux. It has proven successful for us, but try it out yourself: https://github.com/Brigad/redux-rest-easy/ (medium article: https://engineering.brigad.co/introducing-redux-rest-easy-6e9a91af4f59) |
As a highly belated necroing of the thread, a while back I asked for feedback on Twitter for what "boilerplate" means to people: |
Return of the Necro-Thread! Per my edit of the first comment in this thread, the ideas discussed here have been turned into our Redux-Starter-Kit package. Please try it out and see how much it can help simplify your Redux usage! |
Revisiting this thread one last time. We released Redux Toolkit 1.0 in late 2019. Since then, we've made it the default way to write Redux logic, now teach it as the default in our new tutorials, and its usage has grown considerably - in fact, it just overtook Mobx in daily downloads within the last couple weeks. Dan commented in this thread back in 2017 with his thoughts:
Today we've released Redux Toolkit 1.6 with the new "RTK Query" data caching API. While it doesn't normalize and deduplicate requests, it does fulfill the "built in helpers for normalization, collections, and optimistic updates" criteria. It's safe to say that we've successfully achieved the goals laid out in this issue, and we encourage anyone who hasn't tried out RTK to check it out today! |
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.
The text was updated successfully, but these errors were encountered: