reduxjs / redux

A JS library for predictable global state management
https://redux.js.org
MIT License
60.88k stars 15.27k forks source link

Request for Discussion: Redux "boilerplate", learning curve, abstraction, and opinionatedness #2295

Closed markerikson closed 6 years ago

markerikson commented 7 years ago

Resolution: use Redux Toolkit

The ideas in this thread eventually turned into our official Redux Toolkit package. It includes utilities to simplify many common Redux use cases, including store setup, reducer definition, immutable update logic, creating entire "slices" of state automatically without writing any action creators or action types by hand, and even automatic data fetching and caching with the "RTK Query" API.

For more details on what problems Redux Toolkit is meant to solve, see the "Vision for Redux Toolkit" manifesto I wrote .

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.

Phoenixmatrix commented 7 years ago

Yeah, redux's core and react-redux as pretty much as close to a consensus as one can get. Even redux-thunk and redux-actions are pretty borderline, because even at that level the majority of people have different needs.

Reducing boilerplate in Redux is a matter of "I don't need so I'll abstract it away. But even for newcomer, "that part" varies dramatically.

Heck, we have a lot (several dozens) redux apps at work and it's hard to build a common boilerplate reducing stack because different apps have different requirements. It's easy when you have a single app, or a few with the same requirements. Even small ones differ quite a bit.

markerikson commented 7 years ago

The idea here is not to put together a toolkit that solves all problems for all people. Using CRA as an example: there's tons of ways to configure Babel and Webpack and ESLint. What CRA does is offer a single, opinionated way to get them all working without any hassle on the user's part, and it deliberately picks out a number of settings that make things better for learners. It then allows the user to take control of configuration if they want to.

It also doesn't try to solve every single use case. CRA doesn't try to tackle advanced code splitting, multiple entry points, SSR, or stuff like that. The CRA docs do point to other tools and options that may offer more configurability and work better for certain use cases.

tannerlinsley commented 7 years ago

That's a great way to put it. Something to get up and running fast that only involves installing redux and this hypothetical config layer? That would be awesome.

Another idea I had would be a flag you could set in dev mode that could watch for and expose incremental learning opportunities for the user. In this way the abstraction layer could serve as an interactive teaching tool. On Mon, Mar 20, 2017 at 4:14 PM Mark Erikson notifications@github.com wrote:

The idea here is not to put together a toolkit that solves all problems for all people. Using CRA as an example: there's tons of ways to configure Babel and Webpack and ESLint. What CRA does is offer a single, opinionated way to get them all working without any hassle on the user's part, and it deliberately picks out a number of settings that make things better for learners. It then allows the user to take control of configuration if they want to.

It also doesn't try to solve every single use case. CRA doesn't try to tackle advanced code splitting, multiple entry points, SSR, or stuff like that. The CRA docs do point to other tools and options that may offer more configurability and work better for certain use cases.

— 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-287914805, or mute the thread https://github.com/notifications/unsubscribe-auth/AFUmCactFzsw7etmGGP8MFK6kfNaOzTJks5rnvorgaJpZM4MhnVF .

derekperkins commented 7 years ago

@sunjay I knew what xkcd link that was without even having to click. :)

I'm in agreement with @markerikson. This isn't an attempt to handle every edge case for every person. It's an opinionated statement from core Redux devs saying, "this is a good way to get things working". There are sane defaults that can be overridden if needed, but I would wager that the defaults are going to work for 80% of devs.

That may or may not be written on core Redux. I still think there's a place for something like Jumpstate that does more than accelerate the learning curve.

Redux learning curve can be steep, but once you grasp concepts, the need for abstraction layers often goes away

I think that's true to a certain extent, but doesn't necessarily mean that you always want to eliminate the abstraction layer. Even though I have written C in the past and understand memory allocation, I much prefer to write in Go where garbage collection is handled for me. Per my Tensorflow example earlier, it'd be more performant to write to the lower level apis, but there's also a lot to be said for the ease of use of the Python keras abstraction. Similarly, for Redux vs Jumpstate (or whatever abstraction comes from this issue), it isn't that boilerplate isn't necessarily hard to write, it's just cumbersome for most use cases that don't need all the flexibility of raw Redux.

gaearon commented 7 years ago

If it were up to me I’d like to see this:

On top of core, of course, although it’s fine to brand it in as an official Redux thing. Also, I won’t be writing this. But you can.

NE-SmallTown commented 7 years ago

For me,few month ago when I learn redux,I read the doc but don't understand very well,So I spend 2 days to read the src code,then every description about api of the doc become clear.

And then,the thought about the "state design","reducer division","middleware design","async solution"...... chapters become clear too with I develop the project next months.

So,I think read the src code is the fitst step to learn redux,and only after that,you can understand and explore the redux's environment more smooth.

stefensuhat commented 7 years ago

async support by default.

I mean no need to use another plugin such as redux-thunk, redux-saga.

Phoenixmatrix commented 7 years ago

async support by default

It does, it's just that people look at it for 0.5 seconds before looking for something better. Nothing stopping you from calling APIs inside your components and then calling dispatch in the callback/promise resolutions. It's just that people want to externalize it right away (me included). But you can do async just fine without any middleware.

jbellsey commented 7 years ago

@gaearon's comment is perfect, in that it highlights just how broad a sweep there is. I totally love and appreciate the precision and elegance and power of the Redux 2.0 that he proposes, and yet almost none of those improvements are relevant to me!

I actually find that fascinating.

My own Redux abstraction library does almost entirely the opposite. I find immense value in single-reducer actions, in bundling atomic reducer functions with their action codes and selectors, in killing off strings entirely in favor of object keys. It's my own 90% solution, more similar to CRA in attitude than anything else.

The interesting point is very much about: what problems are we trying to solve? There are many problems, with several different solution spaces available. If Dan built Redux 2.0 the way he described, I'd rebuild my own abstraction library on top of that too!

langpavel commented 7 years ago

@jbellsey Redux is Great in providing actions and state to app globally — but in defined scope — you can have infinite instances of redux store but you can combine them. Basically this is useful for libraries. If you see a library talking about redux inside, you can benefit from...

what problems are we trying to solve?

Global state of the App. Reliable aEsthetics Delivered Unified eXtensible :heart:

No one wants globals. But everyone needs one :tm:

Please, keep redux simple as is now. It is pattern. Cookbooks will always arise around :100:

tannerlinsley commented 7 years ago

@markerikson Would be a good idea to start a document / spreadsheet to organize these thoughts? There have been some amazing ideas already and it's only been 48 hours 🎉

dtinth commented 7 years ago

For me, I wouldn’t recommend people to use thunks or sagas. Instead, learn dependency injection techniques in JavaScript.

Redux gets easier once I treat it as a plain database:

For me that’s all Redux is to me (I wouldn’t expect a database to make an HTTP call). For I/O and asynchronous stuff, I perform it outside Redux. This keeps my Redux store predictable. The type signature is always store.dispatch(actionObject). No dispatching functions and no hidden meaning inside the action (e.g. dispatching an action will not make an API call).

markerikson commented 7 years ago

@tannerlinsley: Yeah, go for it.

One downside to me starting this discussion : I've already got a ton of stuff I need to work on and write about. Much of it is self imposed, but it means I don't have enough time to work on some of the Redux-related topics I'd like to investigate, much less tackle things like a major docs revamp or build up a new toolkit from scratch.

I absolutely want to be involved in these possible efforts, but it's going to need other people to join in and work together. (Famous last words of every OSS maintainer... :))

langpavel commented 7 years ago

Redux gets easier once I treat it as a plain database

This is what I recommends!

curran commented 7 years ago

FWIW, I've been using Redux with D3.js (without React) and it's been going quite well. Some examples of this are listed in d3-component.

The thing I miss the most about the React Redux bindings is connect. The point I'd be most interested in from the above outline/vision from @gaearon is "providing mapStateToProps implementations".

Andarist commented 7 years ago

You can prepare data — like views. Do this by dedicated reducers — you then do not need reselect. Consider.

Yeah, this is kinda what twitter does in its store, at least I think so - they have entities branch which contains regulars ids to data maps and i.e. timeline view which is basically the main feed you see which holds id references to the entities branch but its already precomputed.

And thats what Im going for in the upcoming refactors, as reselecting highly dynamic lists is... not too performant ;) the only downside I see is that you kinda end up with duplicated data, and also manipulating updates and deletions to the view might be quite cumbersome in the long run as u need to remember often about 2 places (or more).

iddan commented 7 years ago

@stefensuhat when I tried to implement Redux on my own I used a Promise loop to make async actions possible. You can check it out at https://github.com/iddan/delux

adrienharnay commented 7 years ago

@gaearon totally agree with your list! We've been working on a redux boilerplate following 90% of the list at the organization I'm working (also, following the ducks principes). It's an early release at the moment, not publishable-ready.

I would definitely like to contribute to this good idea!

sunjay commented 7 years ago

@markerikson @tannerlinsley

As for organizing these ideas, I suggest that after you make the spreadsheet you create Github issues in the appropriate repos to track the development. You should then comment here with a list of those issues.

This doesn't guarantee that anything in particular will get shipped with redux, but it does give people interested in that particular thing an opportunity to follow the discussion and work together to make it happen.

The burden of making these suggestions into reality is on all of us that are interested in each new feature or approach. Though Mark started the discussion I'm sure no one is expecting him to go and implement it all. :)

Phoenixmatrix commented 7 years ago

Yeah, @gaearon's list is ideal, but as some people mentioned already, that is a lot of stuff. Anyone attempting to tackle it straight up will face bikeshedding to oblivion. I'd love to see it happen regardless, but I think smaller pieces should be attacked at a time, rather than building a full layer on top (again pushing my example of a preconfigured starter store ;) )

With that said, when/if someone does try to attack that whole list, also keep in mind what other tools and frameworks exist outside of Redux, and even outside of React. React/Redux has an interesting sets of benefits over vanilla Flux, over MobX, over VueJS and it's ecosystem, over Angular. Whatever the community does, it probably wants to keep focus on some of these benefits (some of which are part of @gaearon 's list), and make sure that whatever layer is built on top doesn't become totally redundant. A lot of things like time travel debugging, middleware ecosystems, etc are available elsewhere.

Example I'm making up: it can be tempting make a module that let's you make a function that automatically updates an automatically generated reducer. But once you do that, you replicated MobX, so that's not very useful to do.

stefensuhat commented 7 years ago

@Phoenixmatrix yes what I mean is redux has default middleware , so we don't need to use external one. 😄

slorber commented 7 years ago

I don't really see anything wrong with current implementation of Redux. Boilerplate exists but it's really not a big deal if you have a type system.

One major problem to me is that people don't seem to understand that we are building frontend applications that are way more complex than they used to be some years ago. If we are building applications that are bigger, more complex, with large teams, it's normal to me that the difficulty increase significantly, that new patterns emerge, yet people reject that difficulty.

I'm not saying that we shouldn't try to make things simpler, or reduce boilerplate, but that people should not start to use Redux and complain about the boilerplate involved without understanding the tradeoffs being made and seeing the big picture.

When Redux was created, a lot of people (me included) where trying to build that thing, and @gaearon got the best implementation and did a very good work on tooling/demo. It (and also Flux) got a lot of traction initially, not only because of the time-travel, but also because the concept where not new for many people and immediately feel natural.

People see Redux as a new shiny toy (that seems to end up being not so easy in practice). Yet nothing in Redux is really new except the fact that someone did port those concepts in an elegant way to frontend. The patterns of Redux and its ecosystem are already used in distributed systems, event-sourced backend applications, domain-driven-design, and people having background in these will understand Redux very easily.

Unfortunately, people want to skip steps and use Redux without knowing anything about the more-general patterns involved. Personally I'd recommend anyone wanting to build something serious in Redux to have a minimal background in distributed systems. That may seem weird but I think having that kind of background can give you a very serious advantage right now, and in the years to come.

What are we building exactly when we build a frontend application?

So, we try to build something very complex. We have constraints that are more difficult to handle than database developers, yet we want it to be simpler out of the box, without reusing previous knowledge of people that really thought hard about these problems during lots of years. Don't you think that people already thought about how to structure the state inside a DB? how to query it?

I'm not saying we should not be pragmatic, shipping applications today and make mistakes with our current knowledge, but rather understand and acknowledge that what we are building is not simple anymore and requires additional knowledge. Redux might be a good path to obtain this knowledge, and you can use it in a pragmatic way at first, but don't underestimate how much frontend development will make you uncomfortable in the years to come.

derekperkins commented 7 years ago

Unfortunately, people want to skip steps and use Redux without knowing anything about the more-general patterns involved.

For better or worse, Redux has essentially won the battle for React state management. With that comes the ability to shape the ecosystem. There will always be devs who just want something that works, and there are plenty of simpler applications where that is all that is necessary. Either people will continue to use other abstraction layers, or they can be introduced to the correct general patterns via a simple library.

Personally I'd recommend anyone wanting to build something serious in Redux to have a minimal background in distributed systems. That may seem weird but I think having that kind of background can give you a very serious advantage right now, and in the years to come.

This is the crux of the issue. Redux as is works great for people who understand it. It isn't necessarily a bad thing however to simplify it. Most people aren't writing assembly code anymore, even though understanding it typically helps you to write better code. That's just not where most people start.

markerikson commented 7 years ago

Lots more good comments all around. There's tons of ideas here we could and should explore further.

Let's see if we can start coming up with some potentially actionable ideas. I'll start, by tossing out a small baseline concept:

Create a new package called, I dunno, redux-starter or something. That package might depend on redux-actions, redux-thunk, and redux-logger, and maybe a promise-based middleware. It would provide a function with the signature function createReduxStore({reducers, middleware}) {} (based on @Phoenixmatrix 's comment earlier ). Might not even bother accepting middleware?

The createReduxStore() function would take care of handling applyMiddleware and setting up hot reloading for the root reducer, as well as configuring the Redux DevTools extension. If no middleware are provided, it would configure the included ones by default. This might look similar to the store service setup code in ember-redux or the configureStore function in my "Project Mini-Mek" sample app.

This would at least minimize the amount of setup needed to get started, and provide a modest set of useful functionality.

Phoenixmatrix commented 7 years ago

Yeah, i like that (well, don't like redux-actions and promise based middlewares for newer users, but I'll let you have fun with the bikeshedding on that one). Wouldn't bother accepting middleware, just createStarterStore(reducer).

Maybe even replace combineReducer, so you can simply do:

const store = createStarterStore({
   foo,
   bar,
   baz
});

where foo bar baz are reducers.

markerikson commented 7 years ago

Drat. Just realized that "a library function that sets up HMR for the root reducer" probably won't work, because even if we allowed the user to pass in the path to their root reducer file, one or both of the module.hot.accept() API and require(path) re-import step want paths that are statically analyzable at build time and not variables. (I think... I could be wrong.) Oh, well.

But yeah, createStarterStore() could accept either the root reducer or the slices object.

adrienharnay commented 7 years ago

Personally I'd recommend anyone wanting to build something serious in Redux to have a minimal background in distributed systems. That may seem weird but I think having that kind of background can give you a very serious advantage right now, and in the years to come.

@slorber I agree with you on that part.

Unfortunately, people want to skip steps and use Redux without knowing anything about the more-general patterns involved.

But not really on this one. Some people do know about the patterns involved in Redux, and this is precisely why they chose to use it. Wanting to use something simple (or at least simpler) doesn't necessarily mean the user isn't capable of using a more difficult implementation. It just saves time and effort.

At the place where I work, we've used Redux for a year now, and have found ourselves copying/pasting the same code (only modifying it a bit) to add new resources to the store, which is why at some point we felt the need to implement a layer on top of Redux, to abstract this redundant boilerplate. In my opinion this idea isn't just simplifying the use of Redux, but also reducing the boilerplate for the most common parts (in the same fashion react-redux reduces the boilerplate when using Redux along with React).

arstin commented 7 years ago

Very interesting discussion. My two cents as a redux user since the earliest days:

In the beginning, as I recall it, redux was viewed more as a protocol or set of reasonable conventions than a framework to build apps. This austerity is part of why redux can be overwhelming to new people and tedious to scale without rolling your own abstractions. But the simple way it cut through the mess of managing state in 2015 is also a reason it took off, and the minimalism has allowed for amazing frameworks like Apollo to piggyback on work done for different projects (especially dev tools) and give a familiar way to "drop to a lower level" in state management when need be.

I think it's clear that as a "low level" library redux has been a huge success, and as a feature-rich framework for building apps it's not quite there yet. One thing to be careful about is that as redux evolves it doesn't break it's role as an industry standard "protocol" for projects like Apollo.

To this end, perhaps current redux can become a package like redux-core, and redux itself raises the level of abstraction to give a recommended way of building apps. Of course, there can be any number of higher level alternatives to this redux (like Apollo, or something using sagas?) using redux-core...

markerikson commented 7 years ago

Restating things for clarification and emphasis:

Any work out of this thread will not include major changes to the existing Redux library :) A docs revamp is totally possible, and there might be some small tweaks, but the existing redux package would stay as-is.

Anything like the redux-starter package I just described, or the "framework"-y approach Dan outlined, would be a new package that depends on redux.

tannerlinsley commented 7 years ago

I vote we create the package like @markerikson suggested. Once we have a codebase, we can start batting around api, code snippets and more in-depth ideas. We would then have a place to create issues for specific features.

iddan commented 7 years ago

You can use Delux as a starter (jk)

codinronan commented 7 years ago

As a heavy user of @tannerlinsley's Jumpstate (and now one of the maintainers) I'm incredibly excited about this. My reasons for using Jumpstate are very succinctly outlined in @gaearon's original post. Saying that, it is not a complete solution (it was never intended to be). I was thinking about a few things that could be added on top of this just this morning and then I discovered this thread and found you guys are already thinking about it.

Long winded way of saying: Sounds awesome, I'm in, not sure how much I can contribute (I will try) but you certainly have a cheerleader.

I have used Jumpstate to teach Redux several times now and can confidently say that it has shortened the learning curve considerably. I think of Redux like HTTP and Jumpstate like fetch. I love Redux and have written some (ridiculously) complicated things with it, but I also know that 90% case is more than enough to teach most things.

When you think about it this is a great place to be. One of the core complaints about frontend development and javascript in general is "the lack of standard libraries." Thing is, this is a very new thing (TM), being able to develop full applications in the browser. The pressure is finally rising to need standard libraries. That's where we are now.

mcoetzee commented 7 years ago

Re: Learning curve, opinionatedness and abstraction:

Ajax seems to be a area where new learners get stuck, whether they're learning to use thunks, sagas or epics. One area where they don't seem to have much difficulty is creating actions - as they're just plain old Javascript objects, and we have the added clarity provided by the Flux Standard Actions spec. This line of thinking led me to the question - Why not develop a Flux Standard Ajax Action?

E.g.

function fetchPosts(page) {
  return {
    type: 'FETCH_POSTS_REQUEST',
    // Ajax request setup
    ajax: {
      url: `/api/posts`,
      method: 'GET',
      data: { page },

      // Optional ajax meta attributes
      meta: {
        // Amount of time to debounce the execution of the request
        debounce: 400,
        // Amount of times to retry the request on failure
        retry: 2,
        // Amount of time before timing out
        timeout: 10000,  
        // The action type that cancels the request
        cancelType: 'CANCEL_FETCH_POSTS',
        // Strategy when multiple FETCH_POSTS_REQUESTs are in flight
        resolve: 'LATEST'
      }
    }
  };  
}

The ajax action would be handled by middleware (which would react to the ajax key in the action).

I've been trying to think of reasons why this approach would be a bad idea, but have not yet thought of any deal breakers. Some benefits in my estimation:

I can hear some people invoking the separation of concerns argument, but I would argue that it's a natural extension to what our ajax request actions are doing already. Currently they're setting up an action with a pretty clear context, 'FETCH_POSTS_REQUEST', but with too little context to abstract away the implementation of the ajax request. Leaving little choice but to manually perform the ajax request somewhere else.

Another thing that comes to mind is the async action chaining that I've seen in some docs. We've not had the need for chaining requests in this way, so I'm not sure how widely used this chaining is, but my gut feel is that it would be outside the scope of what I'm proposing here (and maybe doesn't fall within the 90% use case mentioned in this discussion).

In conclusion - it seems quite possible that an abstraction like this would help ease the learning curve and the amount of code necessary to write common Redux applications. Does anyone have any thoughts on this approach - any deal breakers you can think of? @markerikson @gaearon

Prior art: redux-axios-middleware

gillchristian commented 7 years ago

Lots of stuff going on in this thread.

Regarding the discussion about Redux complexity. Personally I disagree with people complains about Redux being complicated and verbose. It's a trade off you make when going with a low level library based on Flux & Elm architectures. It is stated in the Redux home page that you might not need it. But we still have people complaining about it being complicated.

Redux is find as it is. Lucky the core team is aware of it being feature-complete and not willing to change it. The abstractions built on top of it are awesome and each of them have its own use case. This is what you'd expect for a good and well designed low level library.

I see the problem not on Redux itself but on the lack of a progressive, working-out-of-the-box way to learn it. As some mentioned, React itself had the same problem and create-react-app was the solution.

Maybe that is not even the problem since there are awesome resources for that (i.e. Dan's egghead series). Maybe people wanting to use a library that solves complex problems without any learning curve is the problem.

Anyways, this is not what this thread is about.

@markerikson I am personally up to help on developing this redux-starter.

I'd love a cooler name though :stuck_out_tongue:

aaronofleonard commented 7 years ago

The boilerplate required in producing many actions is pretty much the only downside I see with redux.

It is very low-level, but I think that's great. That's one reason why I prefer the Redux-style paradigm over MobX for example, though I do understand it's usefulness and would use MobX. But I'd only be happy doing that now that I understand Redux. Because Redux (or the Redux pattern in general), the data doesn't particularly go "behind-the-scenes". Generally, wherever code is being processed, you wrote it, and if not, you can understand it (if you've taken the time to learn, more on that later).

The thing is, when I was first learning about Redux, it was a lot to understand, but it was also simple. Something can be be both difficult and simple. However because of that, I thought, "Wow this concept is so logical! It makes so much sense I bet I could do it myself!" So for my first project, instead of using the Redux package, I made my own little Redux store.

Doing that not only helped me understand the concept of Redux, but increased my understanding of Javascript itself a lot! (This is from the perspective of someone who knew almost nothing about functional programming concepts before this, so your mileage may vary.) The concept of Redux is really quite brilliant in it's simplicity, yet also incredibly powerful.

I think the thing is that since Redux is so popular, it's, uhh, "buzzwordy-ness" can distract newcomers from just how great it works in Javascript. So their mentality may be "ok I got React, Redux, etc etc, now I'm going to be amazing programmer and make great app!" Not that there's anything wrong with that. But they may focus on just wanting to use a shiny new tool to expedite the development process, without understanding what makes the tool work.

Once the concept is understood, it becomes very easy to get Redux (or any Redux-like pattern) to do all manner of things. I like it for the same reason I like React over other (still great) tools like Angular. It's just Javascript. This is how Javascript works! If the necessary boilerplate can be reduced through some paradigm that would be great, but I think it's a small price to pay for unlimiteddd powaaaaaa.

heyimalex commented 7 years ago

IMO all this "the verbosity is a tradeoff" is just masochism; the verbosity isn't in service of anything, it's a sacrifice you make to use Redux at all. I'm not blaming Redux or javascript, I just want to point out that this isn't essential complexity. Elm manages everything Redux achieves in fewer lines and simpler code.

So we mechanically write out the boilerplate. Jumpstate and similar make life easier, but there are limits to what you can do at runtime, and with typescript/flow jumpstate doesn't help much. But codegen is tough, and no one wants to add one more thing to their already overburdened build system.

If I could have something like Elm code that was translated into idiomatic Redux... Be still my beating heart.

iddan commented 7 years ago

Here are some of my ideas, hope I don't repeat previous comments:

Action creators can be used as action types

actions.js

export function increase() {}

reducer.js

import { increase } from './actions.js';
export default handleActions({
    [increase]: (state) => state + 1
}, 0);

As functions have unique references we can treat them as identifiers for actions. Not FSA compatible but saves a lot of code and prevents mistakes.

Actions can be dispatched asynchronously

async function a() {

}
async function b() {
}
store.dispatch(a());
store.dispatch(b()); // b() will be dispatched after a() resolves

That way action creators can be async functions and yet dispatch one after another. As I see it in many situations people will not need to use middleware based solutions and it will save a lot of boilerplate code. This concept is implemented by a promise factory in Delux

kimown commented 7 years ago

redux === boilerplate

https://github.com/Emilios1995/redux-create-module

but i think this function can completely save your life.

modernserf commented 7 years ago

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: createStore, applyMiddleware, combineReducers, bindActionCreators, and compose. But most of these are convenience functions. Only createStore is strictly necessary for Redux to work. Why do any of these belong in core?

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.

markerikson commented 7 years ago

@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, redux-thunk was originally built right into Redux, and react-redux was only separated out later as well.

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:

As gaearon said once, (I can't remember where... probably Slack) we're aiming to be like the Koa of Flux libraries. Eventually, once the community is more mature, the plan is to maintain a collection of "blessed" plugins and extensions, possibly under a reduxjs GitHub organization.

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?

modernserf commented 7 years ago

Eventually, once the community is more mature, the plan is to maintain a collection of "blessed" plugins and extensions, possibly under a reduxjs GitHub organization.

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 redux package becomes something like @redux/core, blessed libraries like reselect and redux-action are brought into the repo and namespace, and the redux package itself just reexports all of these packages into a single namespace.

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.

We've also got an open PR to React-Redux for adding an object shorthand syntax for mapState, and have had numerous requests for that even though you can do the same thing with Reselect's createStructuredSelector.

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" -- groupBy is really a pattern after all; who are we to say what data structures you use it on?

And every lodash function is also available in its own a la carte library. There's no functional benefit to import { groupBy } from "lodash" over import groupBy from "group-by". Why should I have to deal with tree-shaking when I can just install what I need?

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.

ajhyndman commented 7 years ago

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.

blocka commented 7 years ago

I think this discussion was already beaten to death: https://github.com/reactjs/redux/issues/351

ajhyndman commented 7 years ago

Oh, I wasn't aware of that, thanks! :+1:

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

API

import { 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();
markerikson commented 7 years ago

@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.)

ajhyndman commented 7 years ago

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.

  • What possible abstractions could be created that simplify the process of learning and usage, but without actually hiding Redux (and would hopefully provide a migration/learning path to "base" Redux)?

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!

https://github.com/ajhyndman/redux-event-stream

Koleok commented 7 years ago

@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.

ajhyndman commented 7 years ago

That video is awesome! It's also actually the second item in the list of Thanks on the redux repo's README.

mariusandra commented 7 years ago

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:

screen shot 2017-08-06 at 13 10 03

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 setState.

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 increment and decrement actions or the counter prop, you can just split your code like so:

// 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! :)

davegri commented 7 years ago

Looks nice! not really liking the magic strings though 'increment'