Reasons to Learn Redux as a JavaScript Developer
robinwieruch.de
robinwieruch.de
There's been a lot of chatter on social media over the last couple years suggesting Redux is dying or no longer needed. While it's true that there's a number of other options available, and that there's not as much _need_ to use Redux right away as there used to be, there's still plenty of good reasons to use Redux. In addition, Redux continues to be very widely used, and I've seen quite a few major apps choose to use it over the last year. I talked about these aspects in my Reactathon 2019 talk on "The State of Redux" [0] and my post "Redux - Not Dead Yet!" [1].
We recently released a new official package called Redux Toolkit [2], which includes utilities to simplify several common Redux use cases, including store setup, defining reducers, immutable update logic, and even creating entire "slices" of state at once. It's now our recommended approach for writing Redux logic. It's also written in TypeScript, and the API is designed to provide a great experience in TS apps.
We're currently working on an ongoing major rewrite of the Redux core docs [3], which has several goals: updating content to better match today's target dev audience (removing references to "Flux", etc), simplifying explanations, encouraging easier patterns, and adding additional content and guidance on real-world usage.
As part of that, we've added a new "Style Guide" page [4], which has our recommended patterns and practices for writing good Redux code. We're specifically emphasizing and recommending patterns that simplify the amount of code you have to write, like using Immer for immutable updates, using the "ducks" structure for files and logic, treating actions as "events" rather than "setters", and so on.
Earlier this year, we also released new versions of React-Redux. v7.0 was a complete rewrite of the internals to improve performance, and v7.1 added a new React hooks API [5] as an alternative to the classic `connect()` API.
If you're interested in some of the behind-the-scenes details on how these libraries have evolved, see my posts "The History and Implementation of React-Redux" [5] and "Redux Toolkit 1.0" [6].
If anyone's got any questions, ask away!
[0] https://blog.isquaredsoftware.com/2019/03/presentation-state...
[1] https://blog.isquaredsoftware.com/2018/03/redux-not-dead-yet...
[2] https://redux-toolkit.js.org
[3] https://github.com/reduxjs/redux/issues/3592
[4] https://redux.js.org/style-guide/style-guide
[5] https://blog.isquaredsoftware.com/2018/11/react-redux-histor...
[6] https://blog.isquaredsoftware.com/2019/10/redux-starter-kit-...
Recently there’s some backlash surrounding the use of redux and I’d be interested in hearing about projects (preferably open source so we can see the code) that were using the built in react state management tools and came to the conclusion they were not sufficient.
The createAction function is unnecessarily verbose and the createReducer function obscures more than it abstracts.
In order:
Thunks are the most widely used middleware by a large margin [0], and we have frequently received complaints that there is no async middleware included in the Redux store by default. Thunks were in fact part of the store during the original development phase [1], but were made separate once the middleware API was defined [2]. Thunks are effectively the minimum viable way to write async logic that has access to interacting with the Redux store, so we include that in RTK out of the box to make that possible.
Sagas, observables, and other similar middleware are far too heavyweight and complex for us to endorse as the default async approach. I also strongly disagree that thunks are an "anti-pattern", and find them to be a highly useful tool (and especially so now that `async/await` syntax makes it even easier to write more complicated async logic in a readable form) [3].
You are always welcome to add another middleware or disable thunks as part of the store setup process instead if you want.
I don't see how this:
const addTodo = createAction("todos/add")
is more verbose than this: const addTodo = (text) => {
return {
type: "todos/add",
payload: text
}
}
We've had a version of `createReducer` shown in the "Reducing Boilerplate" docs page [4] for years, and there are literally hundreds of similar "lookup table"-type utilities out there. Including this in RTK is a no-brainer. It's even more valuable, because ours uses Immer inside to let you write "mutative" immutable update logic instead of having to nest spread operators. I don't see how this is "obscuring" anything.Finally, our recommendation is that most of the time you shouldn't even be calling `createAction` and `createReducer` yourself. Instead, you should be using the `createSlice` function [5], which automatically generates action creators and action types for you, and calls the other two functions internally.
[0] Slide 38 in https://blog.isquaredsoftware.com/2017/09/presentation-might...
[1] https://github.com/reduxjs/redux/issues/1
[2] https://github.com/reduxjs/redux/pull/63#issuecomment-110575...
[3] https://blog.isquaredsoftware.com/2017/01/idiomatic-redux-th...
[4] https://redux.js.org/recipes/reducing-boilerplate#generating...
Actions are ALWAYS action(payload) => { type: action, payload }
There's no reason to call a function everywhere you need a new action. Just define all of them in one place by listing the types. You can easily write a function that looks like
createActions(['TODOS/ADD', 'TODOS/DELETE', ... ]})
That returns all your action creators and types in one place. Import them as needed.The above single call function to create a todo action is verbose without achieving any efficiency. It's bad design.
Don't get rid of the switch statements at all. Use them. They're clear. If you need to slice things up, just use child reducers in the switch branches. Lookup tables are less performant and don't buy you anything.
Sorry to be so harsh but this kind of cargo-cult best practice stuff is how you end up with Javascript that feels like Enterprise Java™ from back in the 00s.
We officially recommend consistently using action creators [0], and I talked about the reasons to use them in a blog post [1].
Switch statements and lookups are almost completely equivalent (except for fall-through and default case handling) [2], and "performance" is irrelevant here. In this case, use of `createSlice` means that writing the lookup table object generates the resulting action types and action creators for free, which drastically reduces the amount of code you have to write.
I am strongly against defining async logic inside of `mapDispatch`. It's similar to writing thunks in the first place (an arbitrary function that has access to `dispatch`), but it's less testable, not reusable, _and_ doesn't have access to `getState`. In fact, you should be using the "object shorthand" form of `mapDispatch` [3] to pass action creators into connected components, like `const mapDispatch = {addTodo, toggleTodo}`.
The advice to put async logic in `mapDispatch` also does not help if you're using our new React-Redux hooks API [4], where you just have `const dispatch = useDispatch()`.
The Redux Toolkit API is specifically designed to remove as much as possible of that incidental complexity from your Redux code, and move it _away_ from being that "Enterprise Java" feel.
[0] https://redux.js.org/style-guide/style-guide#use-action-crea...
[1] https://blog.isquaredsoftware.com/2016/10/idiomatic-redux-wh...
[2] https://blog.isquaredsoftware.com/2017/05/idiomatic-redux-ta...
[3] https://redux.js.org/style-guide/style-guide#use-the-object-...
So yes. We disagree strongly but it's because you are outright wrong in your claims. Putting function calls in mapDispatch does not do anything to their reusability or their testability. They're still functions. You're making taste claims but treating them like they are factual.
If you're wondering why my tone is so dismissive it's because yours is so prescriptive. I've worked on a lot of react and redux projects and I know my way around. If you're attempting to reify your development taste into a framework of "best practices" you need to be prepared for criticism. Responding to me with footnoted appeals to authority does nothing for your argument.
The docs content and recommendations are based specifically on the questions I've seen asked, the ways I've seen Redux used, and the concerns I've seen expressed by users. My goal is to simplify how people learn and use Redux, and point them in directions that lead to better codebases. You're always welcome to use other approaches if they work better for you, but there's good reasons for all the patterns we're recommending.
Redux is
(state, action) => state
Sure, wiring it up is clunky without a few helper functions and you're right there should be a set of reified best practices. Unfortunately, the ones you are holding up simply do not buy the developer anything and serve to make the js ecosystem even more impenetrable than it already is.I'm only angry because I know that I'll be wading through piles and piles of garbage as a result of this library.
This gets to a bigger problem with the javascript ecosystem. Someone writes a useful library (s, a) => s, then a hundred other people decide to create "opinionated" frameworks to manage that library. What you're left with are hundreds of arbitrary practices yanked from inside development organizations. They almost always obscure the thing they're trying to illuminate.
Opinionated frameworks are only effective if you get them as a whole package. Rails works because it's every single thing you need for a web app rolled up into one suite of libs. Opinionated Javascript frameworks have been unable to mimic this because the big ones are focused on narrow domains: ui rendering, state management, etc.
This creates an explosion of cargo culted opinions-turned-libraries, which are basically what we had in the 00s with all the Java libraries that were design patterened to death. Believe me, there was a lot of citing going on in those discussions too.
This library will do more harm than it does good if people buy in.
There's no functional difference between using redux-thunk and just calling a "global" `dispatch` in random callbacks; both are equally unmaintainable in the face of interacting effects.
EDIT: Having a bunch of complex async logic all modifying your store in overlapping ways without careful coordination is like having concurrent operations on your database without any transactions or locking.
createAction I don't think we'd ever use, and I'd recommend against it - multiple explicit function arguments instead of a payload blob is much easier to understand when reading the code, and the action function should use those arguments to create the blob. Your example here makes sense simply because the blob appears to be a single string value, rather than say, id+value, or even something more complex.
createSlice, to me, appears to be adding a lot of complexity for no apparent benefit. I even had to read through it a second time before I realized extraReducers was the escape hatch to avoid the action=reducer antipattern, but the structure definitely still seems to be promoting it, where actions are defined as part of a set of reducers, then reused elsewhere if need be.
However, yes, there are many cases where you might want to accept multiple arguments for an action creator, do additional work to define the contents of the payload, or generate unique IDs as part of creating the action. That's why `createAction` also accepts a "prepare callback" as its second argument [0] [1].
`createSlice`'s primary benefit is to remove the need to write action types and action creators by hand. All you have to do is write your reducer functions, give them meaningful names, and you get the action generated creators for free (and the action types effectively become an invisible implementation detail).
We've always told folks that it is not required to have a 1:1 mapping between action types and reducers [2] and that many reducers can respond to a single action [3], but realistically speaking, most actions _are_ only handled by one reducer. Again, `createSlice` handles that 80% use case by generating actions that correspond to that given case reducer in the `reducers` argument, but also allow you to handle the "events" case by listening for other actions in the `extraReducers` argument. (And, the `reducers` argument allows defining "prepare callbacks" as well if desired [4]).
So, in both of these APIs, we solve the 80% use case by default, but allow handling the additional 20% if desired.
Most folks won't need to be calling `createAction` themselves given the existence of `createSlice`, but there's no reason to _not_ have it as part of the public API (especially since `createSlice` already uses it internally).
[0] https://redux-toolkit.js.org/api/createAction#using-prepare-...
[1] https://redux-toolkit.js.org/tutorials/intermediate-tutorial...
[2] https://redux.js.org/faq/actions#is-there-always-a-one-to-on...
[3] https://redux.js.org/style-guide/style-guide#model-actions-a...
[4] https://redux-toolkit.js.org/api/createSlice#customizing-gen...
dispatch => ({
async onRequestFnord(arg) {
dispatch(actions.fnordRequested());
const result = await service.fnord(arg);
dispatch(actions.fnordReceived(result);
}
});
Or something to that effect.All you have to do is change it to:
const onRequestFnord = (arg) => async dispatch => {
dispatch(actions.fnordRequested());
const result = await service.fnord(arg);
dispatch(actions.fnordReceived(result);
}
// later, in the component
const mapDispatch = {onRequestFnord};
At least thunks also give you access to `getState`, and if you need it, the ability to inject an "extra argument" that might contain something like a service reference that can be mocked for testing.[0] https://blog.isquaredsoftware.com/2017/01/idiomatic-redux-th...
I fail to see why adding access to the entire application state is an advantage. This is just cargo culting. In fact that you held up that counter example as a preferable practice tells me everything I need to know about your library.
I've found this to be absolutely required, due to users being able to trigger multiple ajax calls that could return in the wrong order. You need getState() to know which one is valid and discard the old results.
I totally understand that there are legitimate reasons for things to end up that way, but it’s certainly something I’d want to avoid or make as rare as possible.
- https://github.com/reduxjs/redux/issues/137
Any reason why Dan isn't contributing to your new special toolkit, by the way?
He's occasionally dropped by to comment in an issue and leave an opinion (such as when he suggested we drop `useActions` from the React-Redux hooks alpha), but he no longer does any work around Redux.
These days I ask junior devs to learn Elm for a few days. We still use Redux, I will not choose it for new projects.
Also I find classifying people as Redux-proponents funny. It's not a camp, or at least not supposed to be one.
My personal take on the whole JS + Redux stack is that Redux tries to bring order to a language which fundamentally doesn't support certain traits - immutability, custom types, etc. While commendable, it will always just be a band-aid. I wonder if it's time to move to more purpose built languages which do not have these shortcomings (Elm, PureScript, etc)
That said, I'm sure Redux and friends will continue to be used in the years to come.
What I hear from the Redux devs does not in any way line up with what I heard from Brad Traversy or Stephen Grider, who have taught tens (hundreds?) of thousands of people on Udemy that the simplest React projects must be used with Redux and Redux turns beautiful React code into dotcom-era Enterprise Java.
Part of the issue is that the Redux docs _do_ show many of these patterns (`const ADD_TODO = "ADD_TODO"`, separate files and folders for constants / actions / reducers, etc). Dan has said several times that he didn't expect folks to literally mimic everything that was shown in the docs, but tbh that's what people do. He's also said he wishes he'd emphasized teaching it in some different ways.
We're doing everything we can to try to teach better practices and patterns now, but that can't magically change the gazillions of existing tutorials and codebases that are out there.
Isn't that the point of documentation? To give you a reasonable, suggested way of incorporating a library into an existing project? This is almost saying, "the docs were wrong and everyone learned the wrong thing". I don't think you can blame package users for that. People reference documentation to know what to do, and also what to avoid, when using a new library.
That's why we're doing the docs rewrite. The target audiences have changed since 2015 (new devs learning Redux in a bootcamp vs folks coming from other Flux libraries), we've got a better understanding of how Redux should be used, and we want to set folks on a better path from the beginning.
Instead, I advocate using Redux on larger projects - especially as a beginner - because it naturally provides some structure. There is a large community of engineers that advocates using as few libraries as possible. I generally agree with that sentiment, but not for beginner engineers. A beginner engineer doesn't have the skills necessary to architect a large project from scratch (hence the 'beginner' designation), and can benefit from being given a bit more rigid structure.
However, it is exceedingly difficult to find any React tutorial that doesn't have Redux, making it seem to beginners that Redux is required for React. And Redux is entirely overwhelming, especially for a newbie like when I was learning React. It took me three or four courses until I was comfortable and knowledgable enough to strip Redux from my projects, and after that my speed and productivity skyrocketed.
When I was first learning React from your video, I'll admit I turned it off several times, frustrated with the mindset that I was never going to learn React and React was stupid because what do you mean I need to hydrate a store and map state to props and maintain a file listing all of the functions I have? Turns out it's just Redux that I hate. React is fine. And without experiencing the pain that Redux solves, Redux (to me, as a beginner) just seems like completely unnecessary boilerplate that keeps me out of the React ecosystem.
Anyway that's just one person's feedback. I'm sure Redux solves some very real pains, but to me as a beginner, Redux is more of a pain than the problem it's designed to solve (because I've never experienced the problem it's designed to solve) and it is very difficult to find a high-quality course or demo project that doesn't include it.
>What I hear from the Redux devs does not in any way line up with what I heard from Brad Traversy or Stephen Grider
This perception will continue (and only get worse) the more junior devs are educated in trainings/bootcamps that require Redux in your first project. This is the reason so many people have such negative reactions to Redux, they're not yet comfortable with React (which is a new way of thinking by itself) before Redux is added into the mix and it gets very overwhelming very quickly.
Request/response cycle returns a resource, including embedded resources, and we get over it and move on in life.
Now, how on earth a folder-index-sharing protocol for hyperlinking became the planets default UI surface for everything ever, that's another ball of wax to fry another day, killed with the same stone as the mythical 2 birds, etc.
Andrew Clark better explains the difference in this tweet: https://twitter.com/acdlite/status/1195453776217296896
> Redux architecture depends on the ability to block the main thread until rendering is complete. Once you make rendering non-blocking, your store is no longer synchronized with the UI. Leading to data races and inconsistencies.
Many view layers are moving towards non-blocking rendering as they need to account for things such as different priorities based on what the user needs: for instance a user typing needs larger priority than a banner showing.
Once it is non-blocking it's hard to keep a store and several UIs elements in sync. For instance your store might say isLoading:true but the UI might not show that since it's rendering/working another higher-priority element on the screen. And then you might have another view that depends on the isLoading:true which causes another thing to appear on the screen before the other element is shown... all this leads to inconsistencies and race conditions.
Essentially the problem is that we have lots of code that does (especially in thunks):
store.dispatch(FOO)
const state = store.getState()
// here we expect state to contain changes done by FOO
// but with non-blocking UI this is no longer the caseFor context, Andrew is a co-creator of Redux, and is a current React core team member. He's spent the last couple years working deeply on the "React Fiber" rewrite that became 16.0, and the "Suspense" and "Concurrent Mode" features that are now available in alpha.
He's definitely right that Redux's synchronous update model is somewhat limited compared to how React's "Concurrent Mode" needs to be able to dynamically re-order state updates based on priorities. But, it remains to be seen how much of an actual problem this is in practice. Andrew's been heads-down in this feature for a long time, and so it's natural that he's seeing everything through that kind of a lens.
We've got an open issue to discuss and analyze how React-Redux might be able to work better with CM and Suspense:
If a view would just render and dispatch messages on user events, then this wouldn't really be an issue, as far as I understand.
The store and "runtime" can take care of loading stuff, independently of being rendered asynchronously, or not.
E.g. https://sveltematerialui.com/
The Next.js equivalent for SSR is Sapper: https://sapper.svelte.dev/
Note the creator of Svelte is the same guy who created the Rollup bundler so pretty much everything comes with dual configs for both Rollup and Webpack.
https://svelte.dev/examples#writable-stores
What's up with that? A strange semi-singleton where you need to use writable(0)? Also strange that subscribe return unsubscribe. Doesn't seem very intuitive.
Vuex has a nice pattern with plugins and modules.
Mobx stands on its own outside of react. I understand many people don’t consider redux outside of react and don’t talk about one without the other. As other comments mentions the mobx state tree is more a drop in replacement for redux. I am unfamiliar with the render method changes you reference- do you have more info or a link?
Agreed. But the mess they make in MobX is in my experience much more difficult to clean up.
What I meant with render method changes is that mobx tracks access to state during a call to render (which means that anything that might influence rendering, must be accessed inside render). It seems like it's not a big deal, but it can lead to surprising bugs if one is not careful. Redux otoh uses props to pass data to render, which is much more explicit and allows regular react dev tools to be used.
I might have some details wrong - I met MobX a year ago with two legacy apps and it was a hell to maintain. Still is actually, I'm just not that involved in these projects anymore. It's magic, and not in a good way. For new projects we use Redux Toolkit, which strikes a nice balance between verbosity and being explicit.
And Redux is just one big global state
> it is _magic_ - I would have trouble rewriting it from the docs, would you be able to do it?
I accidentally wrote a (bad) version of MobX's core mechanism before I learned about it. At its heart it just uses object Proxies to intercept getters and setters; the former to find out where something is used, the latter to find out when it changes.
It can feel a bit magical sometimes, but the magic isn't hard to understand, almost always just works, and when it doesn't do what you want it usually causes a performance impact, not breakage.
> Note that it also changes the way React render method works.
I don't know what you mean. Aside from "tracking" which observables get accessed within it, all it does is decide when to trigger updates.
I'm not sure who downvotes me, people who successfully used them in production or who dont and don't know they are in for the ride, but for me this is an insanity and I won't touch mobx even with a six feet pole.
People would get too clever and abstract way too much. I don’t see the same with Vuex and MobX but I can understand why you feel that way based on this reply.
The concept of having a centralized state / store with complex apps is impossible to ignore IMO (even if you just have plain objects holding them). At that point sprinkling in a pattern to standardize access isn’t that much overhead.
Biggest problem and reason why people (me included at the beginning) don't like Redux, you should know when you need it and you should use some abstraction layer (eg RTK). One cause could be the docs: while the maintainer try their best to educate a lot (also in this thread paired with interesting link building strategies), there is not quick and easy way to get into Redux. If you start with RTK's docs, you don't understand everything. Then, you need to read the Redux docs, then again back and forth. And this just takes too long to grok an actual simple API.
One easy indicator for Redux or not: does your app need kind of an undo feature, then you should dive into Redux and I'll promise you. there is no other way to do this in such a sane, maintainable way.
When you say "no quick and easy way to get started", are you referring to code or docs?
In terms of explanations, RTK is currently documented as a separate layer on top of Redux, because that's really what it is. So yes, we do assume that folks have learned what Redux is and how to use it already, and RTK is then a faster and better way to write Redux logic instead of doing everything "by hand".
As part of the core docs rewrite, we do intend to completely redo the tutorial sequence, and we'll be introducing RTK somewhere in the tutorials and encouraging its use.
His mind:
- Ok, where should I start?
- Which is the best? Mobx, redux, just ContextProvider or local state??
The poor guys falls into the rabbit hole and reads Reddit, HN, Github issues for hours, still not sure what to do; 2 hours later
- Ok, let's try redux, for whatever reason, the maintainers seem to be quite active
The poor guy tries to decide if he should go just with redux/react-redux or rtk; former because it is good to understand the foundation, latter good because just to avoid boilerplate and that's what this acemark is promoting everywhere. The guy is still overwhelmed and don't forget doing decision is hard; it's one of the biggest struggles for humans
- Ok, I guess I need to start with redux because it's the foundation.
He sees all the theory, new terms, the boilerplate and there's no fast-paced feedback loop; he just think heck and checks out mobx, 4 hours later
- Man mobx is even more weird, totally unstructured and while mobx's maintainer seems to be a nice guy, my gut feeling is: stay away
- Let's check out npmtrends, oh great, npmtrends shows that people also don't like mobx, cool, it has much less traction, decision made, nice
- So, let's try redux again, maybe this time I start with RTK
We know what happens, RTK has better feedback loops but w/o the basic knowledge of redux he will struggle, 6 hours later after jumping back and forth the both docs
- Lets stop here, tomorrow is another day.
He is out of flow, motivation is down, the next day he doesn't continue because the whole experience didn't feel good, he writes a post on HN that redux sucks steel and maybe looks the next week again on it
The situation is a bit like with kubernetes; until you have proper feedback loops with k8s you need days, with new k8s distros this got better but the biggest bummer are the k8s docs, too much, too verbose, so many new terms, no feedback loops at all and every k8s examples has minimum 100kb yamls.
What would be great and I know that writing good docs is much harder than actual code:
One and only piece which the reader should read first, which teaches him redux foundation, react-redux boilerplate and rtk within max 2 hours. After that he should have a working example AND should have understood it. No links to other resources, no nothing.
This main piece should be linked everywhere in the Github readmes of all redux relates projects in bold and h1 READ OUR MAIN PIECE FIRST, on all the doc pages and in all forum posts. I mean look at your post's footer in this thread: so many links, why? You need one entry point. RTK could be the entry point, it just needs a bit more flesh on redux and should be declared as the entry point of redux. There's one disadvantage though: with RTK as entry you'd kill the ecosystem around redux but IDK if you did this anyway by declaring RTK the standard way. And you would have a lot of redundant docs with redux/react-redux. Or maybe we need to merge all because maintaining redundant docs is just too much for an OSS project.
Whatever, all not easy but there must be a way because redux is the most underrated thing in the frontend world, it's def the best state management and RTK is a nice and long deserved abstraction.
Responding a bit further:
There's not much we can do about folks going back and forth between Redux and various non-Redux-y things. That's outside of our scope and ability to help with.
Teaching-wise, there's several reasons why the docs are structured the way they are right now:
- Redux itself is an independent library that is not tied to React in any way (and is used with other frameworks / UI layers as well). Our docs do mostly assume that you're using React, but the tutorials start out trying to show how you'd use Redux without any UI layer, to teach the basic concepts.
- React-Redux has been a separate library from Redux itself since it hit 1.0. While we do teach the basics of how to use React-Redux in the Redux core docs, it has its own separate repo and separate docs site. Similarly, RTK was designed as an addon layer around the Redux core, so it also has a separate repo and separate docs site, and its docs assume that you understand the basics of Redux already.
- That said, yes, the vast majority of Redux usage _is_ with React, and we are encouraging folks to use RTK as the default way to write Redux logic. So, it's very reasonable to have a docs page that shows how to jump right into using both of them as fast as possible. This is something I've already said I want to add to the docs, as a "Quick Start" page [1].
In terms of teaching flow and learning, there's various ways to approach explaining things. Dan Abramov wrote the original Redux docs content and structure, and his teaching style is very much "bottom-up, from first principles". You can see that style in pages like the current "Middleware" tutorial page [2], and his Redux videos on Egghead [3]. That approach works great for some people, but other folks just want to be shown _how_ to do something first, and learn the "why" later. It's hard to serve both groups at the same time.
One issue with teaching RTK first is that while Redux requires / expects that you use immutability correctly, RTK's use of Immer does hide the fact that immutable updates are happening in the background. It would be easy for someone who learns Redux via RTK to assume that writing mutating logic _is_ the right way to write reducers, not realize that it only works right due to the "magic" inside RTK's APIs, and then go write reducers in another project without RTK that are completely buggy as a result. I haven't yet entirely figured out how to handle that aspect, other than plastering warning notes all over the docs. That's one reason why I would prefer that folks understand the core principles first and how to do things by hand, and then switch to RTK to write the same code in simpler form.
As I said earlier, there's good reasons why we have separate libraries and separate docs sites. I can hypothetically imagine pulling things together into a combined docs site, similar to how NgRx does it [4]. But, I'm not sure how technically feasible it would be. It might require restructuring everything into a mono-repo, and I don't think I want to have to figure out how to migrate everything at this point.
All that said, yes, part of the goals for this Redux core docs rewrite is to provide a much more obvious learning path.
Finally, no, I don't think RTK is "killing the ecosystem around Redux", as the ecosystem is much broader than what RTK includes. RTK does obsolete a lot of similar libraries, but there's many other pieces out there for other use cases.
[0] https://github.com/reduxjs/redux/issues/3592#issuecomment-57...
[1] https://github.com/reduxjs/redux/issues/3594
[2] https://redux.js.org/advanced/middleware
ps and to understand your That's outside of our scope and ability to help with. better: Are you not also maintainer of redux and react-redux?
I would seriously suggest just find and replace the word action with event within Redux and the docs. Maybe have one page explaining the history behind this.
_However_, we _do_ have a new "Style Guide" docs page, and in that we tell people to "Model Actions As 'Events', Not 'Setters'":
https://redux.js.org/style-guide/style-guide#model-actions-a...
We'll be adding more pages with info on that topic as part of our ongoing core docs rewrite. I'd suggest pointing people to this style guide entry, as well as these excellent presentations that go into more detail:
- https://github.com/dmmulroy/talks/blob/master/event-driven-r...
- https://youtu.be/K6OlKeQRCzo?t=2626 (video) / https://rangle.slides.com/yazanalaboudi/deck#/ (slides)
That's what I think should be done, with a page noting that historically they were called actions but events is a better name. I think it's the right move to help people understand Redux better and write better Redux code.
There are still people learning Redux for the first time today and the current docs and the name "action" seems to push them towards writing imperative code.
IMO imperative actions are the biggest issue people have using Redux.
I guess a first step in the right direction might be just redoing the docs in an event style. e.g. The counter example would use events like "clickedIncrement" and "clickedDecrement".
As long as I'm giving feedback on stuff, I really like createReducer (builder api) and createAction from Redux Toolkit. I think createSlice encourages the action = reducer anti-pattern.
It's the simplest API I could think of, has excellent type support, and handles immutability for you.
Then again, who hasn’t built a state management framework for React?
The API is perfectly small and straight-forward, it's close to the ideal I imagine.
For a few years I've been using a similarly tiny library I wrote to reduce Redux's verbosity, but I may migrate to pure-store as it's even smaller and gets to the essence of state management.
Seeing the popularity of React hooks and Redux+friends, I know I'm in the minority of people who lament the direction they're going. I'm secretly hoping for a "destructuring" of the React ecosystem into more generic concepts/libraries/modules that work with or without React, carrying on the lessons learned but implemented as simple and independent as possible.
To me, pure-store (and immer for sure) is one of those libraries that point the way to a better future.
After being subjected to the "action types" modules full of redundantly named constants, having to touch five files for a small feature, I simply use Rematch now. Its API is like cake compared to Redux, while it still satisfies the same underlying pattern for consistency/testability.
"I'm going to tell my kids that Rematch was Redux"
In addition, our new Style Guide docs page recommends patterns that avoid those issues as well, such as using the "ducks" file structure:
No thanks Redux, no thanks :/
People making things more complicated than they are is really a disease in the dev community.
Doesn't require a library, let alone an ecosystem of libraries.
I also agree that introducing a dependency at every opportunity comes with serious drawbacks.
Every situation is unique of course. In my case: At work, I value code that is more standardised so that I get less bugs, documentation / best patterns and crucially easier on-boarding process. For a personal project, I would definitely consider rolling out my own implementation, even just for the learning it comes with.
The op of this thread is not wrong but the misuse has more to do with the level of discipline the average JavaScript developer employs than anything.
With great power comes great responsibility!
"Good interfaces are easy to use properly, and hard to use improperly" should be the bread-and-butter mantra of any library author, and Redux violates this repeatedly.
Redux itself has had a pretty rocky road. From its early days of lots and lots of boilerplate, to "ducks" and all kinds of other things over the years. I think the problem was Redux has always been nice in theory, but getting it nice in practice has literally been years of people trying all kinds of things. I think most people's opinions of Redux are largely shaped on at what point they jumped in.
I am having some decent success with the new redux-toolkit. It's finally an opinionated redux offering, which is nice to see. I've also been exploring mobx-state-tree and rematch, and have even had some success with using context coupled with useReducer for a very lightweight redux approach.
If you're looking for a Redux alternative, look no further than RxJS. You get all of the power of Redux without the complex concepts to learn. The Aurelia Store plugin is a great example of using RxJS for state management (it also works with Redux dev tools). A lot of Angular developers will also tell you they use RxJS for state and attest how great it is.
I have seen way too many sloppy poorly performing applications using Redux to know that it is its own worse enemy. The complexity makes for a terrible result in many developer hands.
Ended up not using redux after our previous project failing to scale development-wise (it gets harder to put feature in it). Using unstated, it freed developers from god object paradigm introduced by redux's single global connect() function.
Then we did the same, breaking apart states and putting them in the proper scope.
Also, we also removed state from inter-component operations which doesn't really need state, the ones developer usually workaround using global redux states. We replaced it with custom made event sourcing / message passing library.
Think of it as using rabbitMQ but it is running inside your javascript app.
Things get so much simpler after that. Features can be put it a lot faster. Combined with io-ts, it also support almost 100% TypeScript type checks both on compile-time and run-time.
No blank page issue ever since even without a single React ErrorBoundary and minimum unit-test.
I'm certainly open to seeing if Rx can be used in a nice way. But so far my experience has been very negative.
But with that said, I also feel you can name any state management system (any one at all) and you will find people who love it, and people who loathe it. I feel in general that state management is in many ways an unsolved problem.
Very much disagree. Redux, to it's, credit, almost made development boring to me in the sense that we had an unreasonably large application that could scale and scale and scale without any concern whatsoever. I've worked with other patterns and libraries since and nothing compares to how easy it was for our team (of varying degrees of experience) to jump in anywhere and make additions that made sense through time. And like the author of the article mentions, all of our devs grew in general because of what they learned from it's patterns / concepts.
You kind of have to think about the structure of your state tree beforehand. If you later discover that you sliced your state in several sub-trees with respective reducers, but the reducer of sub-tree A now needs to get some info from sub-tree B, things can get confusing.
Or put another way: if you think parts of your state are independent from other parts and this later turns out to be wrong, you need to refactor quite a bit or have to live with an improperly separated state.
I guess scalability depends a lot on the nature of changing requirements and how experienced you are to define the state tree. This is actually something where I see the biggest learning curve.
For example: say you pull data from your database. This could be considered the normalized state and everything else is just derived from that state. However, in your UI, it doesn’t make sense to work directly with the original normalized DB state, but some kind of semi-normalized intermediate state.
Furthermore: you could put computed/derived state directly in the state tree, or use something like the Reselect library.
Since there are endless possibilities to design this, it can be tough to learn any of this. Most tutorials also deal with very simplistic state trees.
Or do developers just assume that their UI code won't ever need to scale to large numbers of items? (Is this why JIRA's web UI is so slow when you have a couple thousand open tickets?)
But, for most apps, this isn't a concern given the kinds of data and state updates that are common.
There's a specific library called Immutable.js, which implements some complex internal data structures to optimize copying objects. However, the library is relatively large, and complicated some common usage patterns. My opinion is that it's rarely worth using that lib.
If you have enough items that a copy of all attributes is noticeable to the user, the user will never realistically see those items. (We'd be talking about thousands of them minimum) Or alternatively you're loading A LOT of data per item. Either way, the issue in almost every single case is not that the copy takes too long.
I mena, there are edge cases like an endless list of small items (twitter?) which someone scrolls for a long time and then makes a modification. But then you should know it's potentially a REALLY long list you need to handle.
For example in clojure, sequences are implemented as 32-ary tries so insert/delete/update to any particular index will only cost around sizeOfNode * numParents=32 * log_32(n). This comes with a cost that indexing is now log_32(n) but it's usually a fair price to pay.
In javascript, immutable.js implement these same ideas (as least according to these talks where they talk about index tries and hash array mapped tries: https://youtu.be/I7IdS-PbEgI?t=456)
If you want native support, javascript engines are actually free to swap out their implementation of arrays! I know they already do depending on whether they think your array is sparse("holey") or not: https://github.com/v8/v8/blob/8.1.81/src/builtins/builtins-a.... I also vaguely recall someone saying that unshift (push to front) is automatically optimized by switching to a deque/circular buffer but I might be remembering wrong. Anyway, they can make these optimizations in a distant future but I don't think do today.
This is true, but unfortunately those libraries/languages don't make use of that tree structure for "partial memoization" of derived data. They very well could, but they don't -- even when you use Immutable.js in a Redux app, and you use Reselect, your selectors will take linear time when you mutate one element in an object/array instead of taking constant- or log-time.
I think it could be a great technique for a lot of things -- compilers and database views, in addition to front-end state.
TS and immutablejs work together surprisingly well. One enforces immutability via static analysis, and the other creates (relatively) efficient datastructures predicated on the assumption of immutability.
Immutable.js was a bad solution when it was used purely to enforce immutability. Unfortunately that's what its reputation became, but really it should be thought of as a lib that takes advantage of immutable data, not a lib that checks for immutability.
Also available as a free PDF: https://www.cs.cmu.edu/~rwh/theses/okasaki.pdf
Second issue is that developers think they know how Redux works, but there are a lot of gotchas. From example on OP - `return state.map(toggleTodo(action));` With large app, there are for sure cases where such arrays are empty - and this thing will get updated and re-rendered even if it's empty.
From my experience - in most cases MobX is much better than Redux and it's horizontally scalable - as you can just create new, unrelated stores (something that's total PITA with Redux).
If you're using jira's web UI as an example, you'll find the overhead of array and object duplicating to be a tiny fraction of the cost for actually rendering all those items in browser. Probably in the one-billionth or less range of the total cost.
Why spend more time debating whether to learn something than just learn it?
It really isn’t that complicated.
The implications and advantages of all this are huge and take some time to really appreciate, but you shouldn't lose much time being confused and unproductive. The flux concepts of one-way data flow, action creators, etc. are the bigger ideas that take some study.
For this type of things I've always used one or the other implementation of State Machine pattern. Seems way more clear to me.
It is not just about JavaScript. State management is a part of many applications and State Machine is a nice paradigm to work with. At least for my taste.
Note that it's entirely possible to write your reducer logic as state machines, and in fact we explicitly encourage that :
https://redux.js.org/style-guide/style-guide#treat-reducers-...
The useState and useReducer hooks start to resemble Redux a bit more when used together with context, but they definitely do not replace Redux.
For more details, see these posts :
- https://daveceddia.com/context-api-vs-redux/
- https://www.simplethread.com/cant-replace-redux-with-hooks/
- https://staleclosures.dev/from-redux-to-hooks-case-study/
- https://medium.com/javascript-scene/do-react-hooks-replace-r...
- https://dev.to/chrisachard/can-you-replace-redux-with-react-...
The context API is baked in, no external lib required. If you can afford to update your react codebase to the latest version without major breaking changes I would totally migrate to the context API.