Open markerikson opened 4 years ago
Original author: Daniel Culley @daniel_culley
Original date: 2017-01-03T17:51:58Z
Original date: 2017-01-03T18:02:59Z
Ah... yes, I'm aware of redux-loop. Haven't used it myself, but I know what it is and the basic thought behind it.
Did you have something specific you wanted to say about it?
Original date: 2017-01-03T18:03:08Z
Thanks!
Original author: bsingr @bsingr
Original date: 2017-01-03T18:27:13Z
Thanks @markerikson:disqus!
Original author: Daniel Culley @daniel_culley
Original date: 2017-01-03T19:02:45Z
Nothing in particular. It is frequently mentioned and takes a very different approach from thunk or saga, so its absence seemed conspicuous. In particular, it avoids the getState issue because state is always available in the reducer before any effects are dispatched, and it also avoids the serialization problem from thunks. But I also haven't used it other than experimentally. I'm sure it makes some other issues worse.
I thought the article was well done. Probably should have led with that :-)
Original date: 2017-01-03T19:09:03Z
Thanks :)
Yeah, this article wasn't intended as a general overview of side effects approaches in Redux, but rather a response to specific recent complaints I've seen. Overall, I'd say the most popular and widespread libs are roughly thunk, saga, observable, and loop, in _roughly_ that order. (I have links to a couple existing "side effects overview"-type articles in my React/Redux links list: https://github.com/markerik... .)
I think one or two of the libs that try to make Redux even more Elm-like use Redux-Loop under the hood. Definitely seems like a valid approach, I just haven't tried it.
Original author: Mr212 @Mr212
Original date: 2017-01-04T00:19:38Z
Mark, first thank you for this write up.
With that said, I know i'm an idiot but I don't think I'm the biggest idiot in the world, but my journey into the React world continues to make me feel more and more dumb with each passing moment.
I guess I keep having sanity nightmares. I know theres no programming panacea, but it just seems like the original lure of react/redux: components, declarative programming quickly broke down the second we did something as fundamental to modern web apps as...an ajax call?
I'm in awe of the brilliant minds above, but do we mere mortals have to rise to that level of understanding the framework in order to make an informed decision about a core aspect of this paradigm? Is that even possible?
For most mortal developers I feel like we're shuffling/hiding complexity. But what do i know, im just some dude on the 'net.
apologies for the rant. again i really appreciate your contributions to the community.
Original date: 2017-01-04T01:39:25Z
Heh, thanks for the comment.
There's several different factors involved in what's going on here:
First, most of the React community is trying to build full-blown apps, not just static pages with a little bit of interactivity. So, there _is_ some additional inherent complexity in the tasks at hand.
Second, older-style approaches (ye olde "vendor/jquery.min.js" / `$(".the-thing").toggle()`-style implementation) still work. If that approach is something you're comfortable with, and you can write applications that solve your customers' problems (and hopefully are maintainable down the road), great! Don't feel panicked or pressured to use the latest 'n greatest just because it's new.
Third, React and Redux _do_ solve a bunch of problems. But, neither is perfect. There's tradeoffs. Some things are easier with declarative prop passing. Some things take more of a workaround. Some things are easier with global state. Some things are harder. One of the tradeoffs is that both want you to make things explicit, including tracking things like request status. It's _really_ easy to just make an AJAX call and then shove the contents into the DOM using jQuery. That approach involves a _lot_ of implicit state. Tracking that state takes more work, but it hopefully makes the code more maintainable and understandable down the road.
Fourth, a lot of this specific discussion is about idealism vs pragmatism. As I said at the start, I totally respect Dan, Leland, and the others. They're not _wrong_. They're just coming at things from a much more idealistic, "in a perfect world..." angle of discussion, whereas I'm very much a pragmatist.
Fifth, one of my main intents in writing this post was actually to _help_ devs who are earlier in the learning process, by trying to clear things up. In this case, I've seen several questions from people who were taking Dan's comment as gospel. I'm _trying_ to help people understand the situation more, but also encourage a "do what works" kind of mindset. Really don't know if I suceeded or not, but I tried :)
So yeah, I get the feelings of frustration over complexity. There's a great rant called "Programming Sucks" from a couple years ago that's absolutely true ( http://www.stilldrinking.or... ). We're all learners, all of us are clueless about everything except a maybe few topics, and we're all just doing what we can to try to make things a little bit better along the way :)
In the end, lemme throw out one of my favorite quotes: "Make it work. Make it right. Make it fast". You don't have to _deliberately_ write bad code, but imperfect code that does the job beats "perfect" code that isn't complete. Once it works, figure out ways to make it better.
Hopefully that helps a bit. If you've ever got specific React questions, please drop by the Reactiflux channels and ask (http://www.reactiflux.com )! I'm usually there evenings US EST, and there's always plenty of others hanging out happy to answer questions too.
Original author: Francois Ward @francoisward
Original date: 2017-01-04T15:19:43Z
Popularity isn't a very important metric (unless its literally not used) IMO. With that said, Redux-loop is important not because of how usable/good/popular it is, but what it represents, and its historical context.
Originally among the "big" side effect models, sagas and loop were the most discussed. We knew thunks were a hack/compromise (a necessary one), but side effects had to go somewhere. In Elm, actions are always just objects, and the idea is that the UI generates actions, and the reducer (rather, the "update" function) handles everything else. Since in JS/Redux we don't have that kind of pure effect model, the side effects have to go somewhere.
Sagas took the approach of adding a 4th concept (beyond components, reducers and actions) tailored specifically to handle effects in a transactional manner. Loop took the approach of replicating the Elm model where the reducer becomes an update function exclusively responsible for inspecting the action log. It, unfortunately, gets very unwieldy with JavaScript syntax without advanced runtime types. Both have something in common: it makes the effects pure (while thunks are very testable, they require mocking/stubbing which is an extra step. Loop and Sagas do not. When you have 5000+ tests, every little bit counts).
In the end, the Loop model did not really "win", and the community by and large have shifted toward this "4th" concept model. Thunks, Sagas, epics (in redux-observable), actors, etc. The reducer is (almost) synonymous with "store", and side effects happen outside of the vanilla redux model, and the reducer's responsibility is drastically....reduced! (pun intended).
Neither model is really better than the other, its just tradeoffs, but in a discussion like this one, its very interesting to see which set of tradeoffs people converged on, since it has a lot of architectural impacts.
Original author: Francois Ward @francoisward
Original date: 2017-01-04T18:12:32Z
Ill be honest and say I don't like the pragmatic vs idealistic angle. We're all trying to do the same thing: ship quality software to our users as quickly and cheaply as possible with as little compromises as possible. The only question is how we do this and which compromises we make. We're all trying to be pragmatic. A lot of these problems are also solved, they're just not palatable to the community (eg: Facebook is starting to tick in with Reason/ML and BuckleScript. ML has existed since FOREVER, but it's only now that people are comfy with the idea).
These concepts work in the real world (see Elm, CycleJS, etc). They work really well. The communities are smaller for various reasons though, so it's an attempt at getting our cake and eating it too. But rest assured: this isn't an attempt at mental masturbation to make things pretty and shiny. I have deadlines, budgets, and limited resources, as well as countless of -failures- behind me, and so do all the others. So this is simply an analysis of how can we do better.
PHP and jQuery worked fine enough to build stuff like Wikipedia. But for some reason we're doing React/Redux SPAs right now. Why? Because it's better and we didn't stop there. So basically, it's all relative. You say idealistic, I say painfully pragmatic :)
Original author: Francois Ward @francoisward
Original date: 2017-01-04T18:56:35Z
I can't speak for the authors of Redux, but to me that seems to be a side effect (pun not intended) of the roots of Redux: if I understand well, it was a proof of concept to show time traveling debugging and whatsnot. Didn't need ajax requests (or a real app) for that, and so the core was just React, actions as simple objects (reflecting the data types in Elm), and a pure function reducer that act on state, discarding the Effect handling of the Elm Architecture and punting on it. Having the reducer be synchronous makes things a lot simpler.
Afterward though you're stuck trying to make the side effects "fit". My post below explains how Redux Loop tried to "bring it back" by making the reducer return (state, effect) tuples instead of just state, so it can handle everything. JavaScript syntax makes it awkward though.
If you rebuild "Redux" from scratch using Observables (Redux is, after all, based on the idea of functional reactive programming, aka Observables), such as http://michalzalecki.com/us..., or look at things like CycleJS, where asynchronous events are first class citizen, then there's no longer a difference between synchronous and asynchronous actions: instead of everything being synchronous by default, everything assumes streams by default which are better suited for side effects. Those are wonderful models.
But when you think about a real app, the majority of your state changes are synchronous in an SPA. Switching a tab, opening a modal dialog, typing in a form. So there is a strong argument for making synchronous operations trivial, and using an escape hatch (thunks, sagas, epics) for async work. Basically you make the simple stuff first class citizen and push the hard/ugly stuff in a corner. It's a tradeoff which may or may not work for you.
I'm personally on the fence. At HubSpot we have 200ish frontend apps (I think? I lost count) using several permutations of these patterns (not all are even React/Redux), but the apps vary widely in requirements, so sometimes something that worked beautifully in one app breaks down hard in another. At the same time, there's something to be said about using the same hammer all over so that while it's not perfect, you understand the hammer very, very well.
Original author: Jon de la Motte @jondelamotte
Original date: 2017-01-05T22:07:33Z
For once I've been rewarded for reading the comments. Spot on.
Original author: Al-Hussien Khayoon @alhussienkhayoon
Original date: 2017-01-06T15:19:46Z
This is incredible man. Thanks a lot for writing this and please keep it coming!
Original author: Tracker1 @tracker1
Original date: 2017-01-08T16:13:09Z
I tend to like redux-thunk as it works very well with async functions... in this way I can simply have a function that does what is needed with awaits as needed then dispatch the final result in a predictable way... sometimes I'll dispatch a showSpinner() or something similar that will end at the end... While still having the power to expand in those cases. I find having a similar code expression for static action creators vs async and no others is simpler than some of the more complex options like sagas.
To the author, linking to the various npm or github repos for the modules mentioned in the article may help a bit.
Original author: Tracker1 @tracker1
Original date: 2017-01-08T16:18:59Z
I tend to use thunks with async functions as that hammer.. it's been flexible enough for about anything I've needed but only two flows... simple action creator or complex/async action creator, for the most part, and it's worked pretty well. I guess that would be a hammer and a screw driver set.
Original date: 2017-01-08T16:19:20Z
Ah... yeah, I did link to the repos for redux-thunk, redux-saga, and redux-pack. The first mention of each is a link, and I link them again in the "Further Information" section at the end.
Original date: 2017-01-03T18:02:59Z
Ah... yes, I'm aware of redux-loop. Haven't used it myself, but I know what it is and the basic thought behind it.
Did you have something specific you wanted to say about it?
Original date: 2017-01-03T18:03:08Z
Thanks!
Original date: 2017-01-03T19:09:03Z
Thanks :)
Yeah, this article wasn't intended as a general overview of side effects approaches in Redux, but rather a response to specific recent complaints I've seen. Overall, I'd say the most popular and widespread libs are roughly thunk, saga, observable, and loop, in _roughly_ that order. (I have links to a couple existing "side effects overview"-type articles in my React/Redux links list: https://github.com/markerik... .)
I think one or two of the libs that try to make Redux even more Elm-like use Redux-Loop under the hood. Definitely seems like a valid approach, I just haven't tried it.
Original date: 2017-01-04T01:39:25Z
Heh, thanks for the comment.
There's several different factors involved in what's going on here:
First, most of the React community is trying to build full-blown apps, not just static pages with a little bit of interactivity. So, there _is_ some additional inherent complexity in the tasks at hand.
Second, older-style approaches (ye olde "vendor/jquery.min.js" / `$(".the-thing").toggle()`-style implementation) still work. If that approach is something you're comfortable with, and you can write applications that solve your customers' problems (and hopefully are maintainable down the road), great! Don't feel panicked or pressured to use the latest 'n greatest just because it's new.
Third, React and Redux _do_ solve a bunch of problems. But, neither is perfect. There's tradeoffs. Some things are easier with declarative prop passing. Some things take more of a workaround. Some things are easier with global state. Some things are harder. One of the tradeoffs is that both want you to make things explicit, including tracking things like request status. It's _really_ easy to just make an AJAX call and then shove the contents into the DOM using jQuery. That approach involves a _lot_ of implicit state. Tracking that state takes more work, but it hopefully makes the code more maintainable and understandable down the road.
Fourth, a lot of this specific discussion is about idealism vs pragmatism. As I said at the start, I totally respect Dan, Leland, and the others. They're not _wrong_. They're just coming at things from a much more idealistic, "in a perfect world..." angle of discussion, whereas I'm very much a pragmatist.
Fifth, one of my main intents in writing this post was actually to _help_ devs who are earlier in the learning process, by trying to clear things up. In this case, I've seen several questions from people who were taking Dan's comment as gospel. I'm _trying_ to help people understand the situation more, but also encourage a "do what works" kind of mindset. Really don't know if I suceeded or not, but I tried :)
So yeah, I get the feelings of frustration over complexity. There's a great rant called "Programming Sucks" from a couple years ago that's absolutely true ( http://www.stilldrinking.or... ). We're all learners, all of us are clueless about everything except a maybe few topics, and we're all just doing what we can to try to make things a little bit better along the way :)
In the end, lemme throw out one of my favorite quotes: "Make it work. Make it right. Make it fast". You don't have to _deliberately_ write bad code, but imperfect code that does the job beats "perfect" code that isn't complete. Once it works, figure out ways to make it better.
Hopefully that helps a bit. If you've ever got specific React questions, please drop by the Reactiflux channels and ask (http://www.reactiflux.com )! I'm usually there evenings US EST, and there's always plenty of others hanging out happy to answer questions too.
Original date: 2017-01-08T16:19:20Z
Ah... yeah, I did link to the repos for redux-thunk, redux-saga, and redux-pack. The first mention of each is a link, and I link them again in the "Further Information" section at the end.
Original author: Francois Ward @francoisward
Original date: 2017-01-08T22:04:19Z
If your async logic is mostly imperative, async functions in thunks is a pretty good hammer.
The problem is that in a moderately complex application, that frequently is not the case. Or even worse: the requirements are there, but people don't implement it (leading in a degraded user experience) because it feels too hard/not worth it.
Think of the following scenario:
You have a dropdown, and as the user type, you want to fire off API requests to provide auto-completion. If the person types as the API requests are coming, for best performance you should abort the previous API requests and start new ones. Since you don't control the server, the API requests could return in a different order than they were started. You always want to show the autocomplete that represents the last thing the user typed, obviously. And if the user navigates away while the API request is running, you want to abort it there too (browsers have a limit to how many API requests they send and you don't want that limit to slow down loading the other screen).
All of the above in something like redux-observables is just a few lines of code and is basically a "hello world" scenario. In a thunk it's....rather difficult.
Generally what will happen with the scenario above is most people will just say "meh, we won't abort the requests, it's not worth the trouble for a small perf gain. Besides, fetch doesn't support cancellation because of promises". But with other tools, it takes seconds to implement it...
Original author: None
Original date: 2017-01-30T19:39:50Z
None
Original author: Matt Granmoe @mattgranmoe
Original date: 2017-02-28T19:36:58Z
"My post below explains how Redux Loop tried to "bring it back" by making the reducer return (state, effect) tuples instead of just state, so it can handle everything. JavaScript syntax makes it awkward though."
This type signature reminds of how your update function in Elm usually returns a tuple of (Model, Cmd Msg). I'm very curious to read this. Where is the post? I couldn't find it! :-)
Original author: Francois Ward @francoisward
Original date: 2017-02-28T19:50:18Z
Here's the perma link:
And yes, Redux Loop is basically trying to make Redux reducers into Elm update functions. That's on purpose.
Original author: Gregory Beaver @gregorybeaver
Original date: 2017-03-29T03:54:49Z
I'd like to mention that I would like to see a post that examines these options in terms of testing and technical debt rather than just abstractions of power/unidirectional flow. I have found that sagas are by far the most isolatable in terms of testing. Each saga has to be tested in its full sequence, but if you make a minor change to other portions of the app, it doesn't cause your saga tests to break. All of this without complex stubbing/mocking (which in my experience causes test to pass and the app to break when you change stuff).
Observables/epic/whatever-they're-calling-it-now essentially create a hidden binding that rears its head in testing. They tightly bind the thing that uses the observable to the implementation of the observable. To their credit, the docs mention this as an issue that makes testing much more complex, and they are actively seeking a brilliant solution. Thunks do a similar thing by being tightly coupled to actions. By making side effects a natural part of the redux action->reducer->view flow, it makes it much harder to test the side effects as side effects. Instead, they become simply a complex part of the regular flow rather than something on the side that pokes normal actions into the flow.
If you're not unit testing, then other methods than sagas will work just great, probably better. But in my experience, if you REALLY want to be able to split up/delete/improve an app, you need tests that are properly isolated so you can split without refactoring 300 unrelated tests. And you can't refactor without tests, unless you like Russian roulette. And in that case, might as well just code the whole damn thing in MS Access 2000 Visual Basic.
Side note: the absolute worst thing you can do for an app is try to make it "future-proof" and "reusable." Just make it easy to delete your code and replace it. Remember how OO was going to make reuse so much easier in the future? Yeah... Past you always has a lower IQ than future you.
Original date: 2017-03-29T04:00:48Z
Yeah, I think there's general agreement that sagas are more testable than the other side effects approaches. To be honest, testing is still a weak spot in my own repertoire - although I'm very familiar with the concepts, I don't have a lot of practice.
I do have a number of articles comparing different side effect approaches in my links list: https://github.com/markerik... . I'm not sure how much those articles talk about testing specifically.
Original author: Aaron Planell López @aaronplanelllpez
Original date: 2017-07-12T06:59:03Z
Sorry for the delay. If I'm not confused, it's an action that calculates part of the state of the store and send it to the reducer to set it. Actions must say 'what happened' and reducers must say 'how affect what happened to my piece of state'. If you calculate the piece of state in the action you are not decoupling the 'what' of the 'how'. If reducers only set data, that anti-pattern is called 'glorified setter'.
Original author: mcpetry @mcpetry
Original date: 2018-07-31T08:26:58Z
Another alternative for redux-saga and redux-observable is redux-handler (https://github.com/mc-petry.... Try it with TypeScript
Original author: Andy Edwards
Original date: 2018-09-04T23:57:52Z
I've used Redux for years without thunks or redux-saga. Instead, I just write my own middleware to handle side effects and async requests. Not that hard. Unlike thunks, all actions are guaranteed to be objects, and it's way less confusing than redux-saga.
Original author: Andy Edwards
Original date: 2018-09-05T00:02:53Z
I think the bigger issue with Redux is, although it's theoretically ideal, it has a fundamental scalability problem. One of my projects draws data-heavy plots that the user can drag around, and I had to optimize HARD to get anywhere close to 30 fps when dragging, since that was 30 actions going through redux and 30 copies of the entire app state getting created every second. It was only possible by storing state in Immutable.js collections and writing my state selectors very carefully with Reselect. And it came out no less complex than a more old-fashioned MVC with tons of different event listeners type approach.
This is why very different approaches like MobX have become popular.
Original author: Betsy Gottesman @betsygottesman
Original date: 2018-09-12T22:16:45Z
I've had a similar issue in the past with animations and Redux. One of the issues with Redux is the documentation kind of hand-waves the reason why you will need selectors in any large app, so most people (including me) just ignored it when starting out. The other thing is the question of "do you always need Redux for all of your state or can you ever use internal component state" is never really answered in the documentation, so people tend to adopt Redux and then put the entire application's state in there, including small and fast UI changes that no other part of the application needs to know about.
I have since come around to storing the state of animations (like drawing a box, listening to page scrolls, etc) inside the component where they are happening, and then persisting the final state of the animation (the box dimensions, the current Y position of the element, etc) in Redux if necessary. I find this much more simple and performative than adding in all these plugins just to have inherently transient state changes saved in Redux just for the sake of using Redux.
Original author: Andy Edwards
Original date: 2018-09-12T22:35:52Z
Yup. In my case I guess the time range being animated needed to be shared among enough different components that it was hard to put it into some local state, though I probably could have done it if I tried hard enough (and persisted the final state like you say).
And I've gotten more comfortable with sometimes storing selected items and other things in local state when I don't need to access it outside the component, because it's quick and easy to implement, and I could just move it to Redux later if some new change makes it necessary. It never ends up being a painful refactor because local state is inherently restricted to a single component.
Original author: Andy Edwards
Original date: 2018-09-12T22:37:17Z
Even though I think Redux is great for a lot of cases, If I were tasked to build a complex app like a 3D CAD program or Ableton Live in JS, I wouldn't use Redux for it.
Original date: 2018-09-13T00:18:35Z
We do talk about selectors in the Computing Derived Data page in the Redux docs, as well as the FAQ pages on performance and React-Redux Dan also talks about them extensively in his second Egghead video series.
The "component state" question has its own specific FAQ entry.
FWIW, we're working on revamping both the React-Redux docs (which were basically nonexistent), and the main Redux docs as well. We'll try to highlight use of selectors and such more in that process.
Original date: 2018-09-13T00:20:42Z
FWIW, I know that Reddit user /u/drcmda actually works on a "3D CAD program" that is built with Redux.
Also, it's possible that future versions of React-Redux may have more sophisticated optimizations available
Original author: Andy Edwards
Original date: 2018-09-13T04:20:13Z
Huh, interesting...
I had another idea recently. I was thinking about the difficult scalability issues involved with tracking which files are selected in a file tree component, and I was wishing I had the option to control when `connect` updates by firing events (from a middleware) and telling `connect` which events to update in response to.
Of course the other solution would be to use `react-virtualized` for that tree to limit the number of component instances rendered, which in general is a good idea. Though it would be impossible to animate expanding/collapsing in that case.
Original date: 2018-09-13T04:28:48Z
This comment thread probably isn't a good place for that discussion. If you have questions about how to optimize usage of `connect` or how we're planning to work on future versions, come drop by the Reactiflux chat channels on Discord. I hang out there evenings (including right now), and there's plenty of other experts there too. You might also want to read https://github.com/reduxjs/... .
Original author: Betsy Gottesman @betsygottesman
Original date: 2018-09-13T16:49:24Z
No offense, I don't mean to rag on you, but those FAQ pages are literally the definition of "hand-waving" in my opinion. You're talking about a one-line off-hand mention of memoized selectors in each page as if the documentation was "talking about it".
The truth of the matter is the Computing Derived Data page is the only real discussion of selectors in your documentation, and even then, I don't think the docs accurately reflect the WHY of using selectors, namely how important for performance they are. "Reselect can help to avoid unnecessary recalculations" really makes memoized selectors sound like a nice-to-have, rather than a necessary tool in a large or even medium sized application. Are selectors necessary enough to belong in a "basics" or even "advanced" discussion of Redux? Then why are they relegated to an afterthought in "Recipes"?
Original date: 2018-09-14T00:43:25Z
You do have some valid points there. The larger issue, though is that the Redux docs really don't have any systematic guidance for building a larger "real-world" application. The "basic" tutorial builds a todo app, the "advanced" tutorial queries data from Reddit, and... uh... then there's a bunch of random grab-bag "recipes". Really, the majority of the "real-world" advice is in the "Structuring Reducers" recipe and the FAQ, both of which I wrote.
There _is_ plenty of community discussion around selectors and why they exist. I myself wrote an in-depth article on Idiomatic Redux: Using Reselect Selectors for Encapsulation and Performance, and there's plenty of others listed in the Redux Reducers and Selectors and Redux Architecture sections of my React/Redux links list. So, the knowledge is out there, but you have a point that it's not as well highlighted in the existing docs as it should be.
That said, we _are_ starting a major revamp of the docs, and you can see our discussions so far at https://github.com/reduxjs/... . As part of that, we're definitely going to have a section on "real world app architecture", and that will absolutely include discussion of selectors. We're also starting a rewrite of the React-Redux docs (which are basically a giant jargonistic API reference), and that will cover selectors as well. See https://github.com/reduxjs/... for our plans there.
If you've got any further suggestions or comments, please let us know in those issues. Thanks!
Original author: Odin Hørthe Omdal (odinho/Velm @odinho
Original date: 2018-12-16T12:03:23Z
Interesting. So you listen for certain actions in your middleware and call async stuff based on it? Dispatching fetch success etc when done from that middleware? That sounds like a usable design. Where do you place the middleware? In the relevant duck?
Original author: Andy Edwards
Original date: 2018-12-16T16:22:55Z
Right, either I dispatch fetch success from the middleware, or have the middleware return a promise that dispatch caller awaits.
Original author: Andrii Golubenko
Original date: 2019-02-27T19:48:15Z
Sorry, but how you test your middleware?
Original author: Francesco Belladonna @FireDragonDoL
Original date: 2017-01-03T15:45:51Z
Nice read!