by author Victoria Lo
In this article, let's learn about Redux Toolkit, or as the development team calls it, "the official, opinionated, batteries...
For further actions, you may consider blocking this person and/or reporting abuse
Redux Toolkit seems super opinionated to me. Opinionated libraries are fine but I think RTK takes it a bit too far. Redux has a very simple API and I feel like RTK introduces a lot of new concepts that could overwhelm new users. I'm sticking to typesafe-actions and Immer for now but maybe someone can convince me otherwise 😆
Which "opinions" are you concerned about?
The only real complaint we've gotten is actually that RTK's use of Immer is not configurable, and that's specifically because Immer makes your reducer logic much simpler and eliminates accidental mutations, which have always been the #1 type of bug in Redux apps. So, that particular "opinion" is non-negotiable. Other than that, all that RTK does is take common patterns that everyone was already doing anyway, and simplifies them.
Beyond that, RTK is already written in TypeScript, and we've designed its API to minimize the amount of types you have to declare. Example:
All you need to declare is the type for this slice's state, and the payload type for each action, and RTK will infer the rest for you. See redux.js.org/tutorials/typescript-... for a summary of recommended TS setup.
It seems like I may be in the minority here, which is fine 😄. I also don't want to detract from your work on the library!
I suppose the three parts of RTK that I consider very opinionated are:
createAsyncThunk
— this seems to be specifying a side effect model for Redux. I've only ever used Redux Saga for this so I don't really understand thunks. Maybe if I used them I would understand why this was included in RTK.createEntityAdapter
— I always thought the Redux philosophy was to design action types, reducers, and selectors specific to each entity since different entities usually have different operations and filtering logic. I can seecreateEntityAdapter
being useful if you have a ton of very straightforward CRUD in your app.Just my two cents. I know these are all optional features and that tree shaking will probably remove them if you don't use them.
Responding to each of those items:
createAsyncThunk
Yes, we do specifically recommend thunks as the default approach for side effects in Redux. That does not mean it's the only approach you can use. All the existing side effects libs (sagas, observables, 5 million others) still work fine. But the most common side-effect-y thing people need to do is basic AJAX fetching. Thunks are the simplest approach to doing that, and they have also always been the most commonly used approach.
FWIW, we're actually trying to nudge people away from using sagas as a default approach, especially for just data fetching. Don't get me wrong, sagas are a great power tool for complex async workflows that involve debouncing or "background thread"-type behavior. But they're overkill for basic data fetching, and one of the contributing reasons why people have long complained about the "boilerplate" associated with Redux. They shouldn't be the first tool people reach for when they want to fetch data, and they shouldn't be something that's "required to use with Redux".
So, since people were already doing this anyway, and the docs have always taught this "promise lifecycle actions" pattern from the beginning, it made sense to add
createAsyncThunk
to simplify the code that people were already writing.References:
createEntityAdapter
We have always recommend storing items in a normalized lookup table. In fact, I specifically wrote the Structuring Reducers: Normalizing State Shape docs page back in 2016 to officially start teaching that concept. But, we never had any built-in APIs that helped with any of that process. For any collection of items, you are very likely to do a number of CRUD-y type operations.
So,
createEntityAdapter
provides the reducers that implement those operations, and it's up to you to decide when and how it makes sense to run those reducers, and in response to what actions. They can be used as complete "case reducers", or as "mutating helper functions" inside of other Immer-powered reducers. Again, we just looked at "what are things people commonly have to write on their own?", and provided some generic implementations. You're free to use them, or not. All these APIs are pick-and-choose.RTK Query
Aas mentioned with thunks, users have always used Redux stores as a server state cache. It's just that Redux never included anything built in to help with that process. So, you inevitably ended up having to write all your own logic to manage loading state, make the requests, define the actions, dispatch the actions, save the data, handle errors, etc. The growth of React Query and Apollo have shown that the community has become much more interested in "server state caching" than "state management" in many cases. Redux makes a perfectly fine place to cache data, but writing the code to do that yourself is a pain.
So, once again, we built a set of APIs that just simplifies what everyone else has been doing by hand already, and provides a best-in-class API for dealing with server state caching.
RTK Query is built on top of
createAsyncThunk
and the other APIs in Redux Toolkit. As the RTK tagline says, it's "The official, opinionated, batteries-included toolset for efficient Redux development". Yes, RTK Query is effectively a second library, but it it's also very reasonable to include it in the actual RTK package. For one thing, some enterprises have difficult approval processes to go through to add additional libraries. By including the RTK Query APIs directly in the RTK package, you get them "for free" just by bumping to RTK 1.6+.And yes, not only do the RTK APIs all tree-shake so you only pay for what you use, RTK Query itself is an entirely separate entry point, so none of it gets included in the app unless you specifically import it.
As you can see, all of these APIs have a common theme:
Hopefully that helps clarify why these APIs exist :)
Awesome response Mark. That really clarifies why those APIs were included.
You've really got me thinking about this and I'm planning to write a post about my thoughts on RTK, both the pros and the cons. I will link it here when/if it gets completed.
Sure, please do! I'd also suggest reading through my Redux Toolkit 1.0 post, which goes through the entire history and background of why RTK was created, what problems it tries to solve, the "vision" for the library, and the evolution of its development and APIs up to the 1.0 release.
Obviously I'm biased, but the only meaningful tradeoffs I personally see in using RTK over "vanilla" Redux are:
Proxy
, debugging can be harder because logging or inspecting the draft shows theProxy
instead and the contents are very hard to readOn the flip side of those:
current
util to view the current state if neededSo yeah, RTK certainly isn't perfect, but I can only think of a couple very minor concerns, and those are in turn far outweighed by all the benefits: standardized APIs, smaller app code, and prevention of common mistakes.
My post is here.
I hope I succeeded in providing an accurate and balanced review of RTK. At the end of the day, it's all just my opinion.
We adopted Redux when it first came out. While you are correct in that the API is very simple, and the strategy of Redux is simple, implementing state with Redux is anything but. I recall making several passes through the tutorials to grok the what is required to implement Redux in an app. Configuring the store, configuring the 'mother reducer', learning about HOCs, considering implications on the rest of the architecture, implementing selectors, unit testing, state "slices" and what that could or could not mean, leveraging the 'backloors' (ie 'ownProps') it was really overwhelming to me. After taking a hiatus from React for Angular, I found myself architecting a new React app, encountering RTK for the first time.I got to say that my impression was that it does a really good job in building the right abstractions for an engineer just trying to architect an app and manage its state. Granted that I would not really be qualified as a 'new user', I thought it was a step in the right direction for the purposes of Redux adoption. I would argue that the new concepts of RTK are also present in vanilla Redux, but in a more transparent way, IE the considerations I had just mentioned. Thinking about it, RTK is really just an abstraction that wraps vanilla Redux, essentially putting it on rails. With any rail system, you trade in some flexibility for safety in terms of removing chances for misuse. In that respect, RTK adoption should probably considered on a case by case basis. my 2 cents 😊.
You're not alone on that, man. While forced Toolkit and opinionated decisions are understandable and they do cover probably 90% of developers that use Redux, I as well think it's too opinionated.
One of advantages of Redux is very simple API surface that is easy to reason about. Boilerplate code is nothing in big projects (for which Redux is suited), if you think Redux has a lot of boilerplate, that means your project is too small to benefit from 1 action - many reducers pattern and Redux in general.
I personally don't like Immer as well - whole point of Redux is functional pattern, focused around immutability and pure functions (reducers). Instead embracing pure functions, Toolkit tried to hide purity by introducing magic mutations by Immer (yes, I understand they're not mutations under the hood, but it doesn't matter - Immer and code that LOOKS like mutation does not compose). For me Redux looks much better with functional libraries like Ramda.
And lastly, in my opinion slices force connection between actions and reducers - while in idiomatic Redux there is none. The whole point of Redux is to separate one from another, not connect them to "slice".
I think this is a misunderstanding or mislabeling of "pure functions".
Immer-powered reducers are pure functions. They produce the new state immutably, and they do not actually mutate any values outside the function. It's just that the immutable updates are done via mutating syntax. (And fwiw, Dan Abramov has pointed out just how useful something like Immer is as well.)
I would also argue that
createSlice
in particular mostly shrinks the Redux API surface, because you're no longer having to write action creators and action types yourself - those now become an implementation detail that you no longer need to worry about.as Redux maintainer, I would disagree with this. I've seen thousands of Redux projects of all sizes, and trust me, the "boilerplate" complaints were real. Fortunately, RTK solves them.
It didn’t overwhelm me at all when I 1st got into it, quite the opposite in fact. Plus RTK uses Immer under the hood. 👍🏾
I used to hate Redux, until Toolkit came out
this 1000000 percent
It's not really true about reducing amount of boilerplate, I went through refactor in recent projects that introduced RTK and in fact it reduces just one file from 3 that are being used: actions / reducers / selectors
Code has became much less clear and in the result readable.
Im not mentioning the case of forcing ppl to use redux thunk as it's included in RTK package, personally I do prefer Saga over Thunk.
Imho it's not that worthy as It has been described above.
Can you give an example of the before and after here, and show some RTK code that you feel is "less readable"?
This is a misunderstanding. Nothing about RTK forces you to use thunks. You can still add the saga middleware to the store, use that, and ignore thunks entirely if you want. RTK simply adds the thunk middleware to the store by default, and provides a
createAsyncThunk
API to simplify the common thunk usage patterns, but you are not at all "forced" to use thunks whatsoever.Actually it is as RTK includes ReduxThunk by default, copied from redux official page:
It includes several utility functions that simplify the most common Redux use cases, including store setup, defining reducers, immutable update logic, and even creating entire "slices" of state at once without writing any action creators or action types by hand. It also includes the most widely used Redux addons, like Redux Thunk for async logic and Reselect for writing selector functions, so that you can use them right away.
RTK is less readable as it comes with Typescript, everything combined within one slice is less readable, apologies however I can't provide you with all code samples that made me feel its not clear :D
It's a pity that the solution to Redux complexity is to add yet more JS code, rather than to give it the major refactor it really needs. IMO it suffers from over-use of currying for configuration, which is "clever" but makes learning and using it way more difficult than it needs to be. The definition of actions and reducers is repetitive and complex with code for a single thing spread out in multiple locations which again, in my experience, makes it really difficult for people to learn.
Although I loved what Redux ultimately provided (predictable state), the way it went about it was so convoluted it made working with it a chore. Tools like Rematch mask some of the issues but the fundamental problems can't be solved with wallpaper.
I had a go at creating something similar to Rematch, which was based on the same premise (reversing the store definition to make things easier) but done as a complete re-write rather than a veneer on top, and aiming to provide full strict Typescript support:
captaincodeman.github.io/rdx
Why do we need to use this action/payload and then switch on the action type?
We are going to write code in those switch blocks. Why are we not just writing a function:
doMyAction(payload)
?I totally agree with what you are saying. I don't see the need for
Context
as we have JS modules.import
works really well. There are plenty of more maintainable patterns; singleton, factory, etc...When I see the whole { action: 'ACTION', payload: somePayload } I think of the rare times I use some broadcast tooling. Event emitting can be fun, but over time it, too, ends up being complexity that is rarely needed.
I understand wanting predictable state 10000%. How my teams maintain that - unit tests. I'll stop this over-reply to emphasis that for newer developers. When it comes to your state and business logic, write unit tests. Get as close to 100% coverage as you can. I'm more lax about testing UI components (it is still good), but whatever state code you use - test the shit out of it.
PS. Simon, always love your thoughts. I remember how prolifiic and helpful you were on the Polymer slack channel. I still wait for WebComponets to just replace React components. LIT? Maybe?
Because "just functions" does not provide the properties needed to enable Redux's design goals, including serializing a history log of "all the things that happened in the app" and "allowing middleware to modify actions".
See the list of goals under The Tao of Redux, Part 1 - Implementation and Intent: The Intent and Design of Redux.
I'm not a huge fan of middleware either. It is sort of dispatch -> mystery transformation -> reducer. Entering a codebase with a lot of middleware can be super confusing.
This is not elegant, but an example
myMethod(aTransformer(payload))
another developer coming in knows exactly what is going on. The downside is that you will be typing in that transformer over and over. If the middleware is fetching data, well.fetchFn(payload).then(myFunction)
I also totally get the idea of serializing events. I have built many API services that do something similar. I often use something like jsonpatch.com/ where I have a data object that I can apply, unwind, flatten, etc... It is pretty easy to have a function
getPatch(prevState, nextState)
then drop that in a Set/Array/Database/etc...I'd argue that something like a JSON Patch becomes more useful anywhere. What you define as { action: "someAction", payload: "somePayload" } is not always a straight up merge. Often the payload is a single key in the state. Or there is business logic in the reducer. Thus, you need to know how the code works and if you change the the code, you break all previously stored actions if you needed to reapply them.
Of course, I'm talking about actually storing event history. In reality, how many React develpers do much more than a simple
undo
and even then I rarely see that. What I do see is that developers use the actions in Redux tooling to see if their event fired and what side effects might have happened. And right there I would say build that all in a Jest test and check your results there with assertions. Like, actually develop in your test runner. Then you have test coverage and you'll know if your state breaks with some code/dep update.What specific concerns do you have?
The only uses of "currying" I can think of are writing middleware, which is a very rare use case, and the
connect
API, which has basically been replaced byuseSelector
.Looking at your linked lib, you show this example:
RTK's
createSlice
is effectively identical to that:and RTK is written in TS and designed for a great TS usage experience:
redux.js.org/tutorials/typescript-...
The only immediate difference I see is that RTK's action creators and reducers are still standalone functions, rather than being attached to a
store
instance.And that's the whole point. Redux is about separating what happend from how that changes the state. Without separation there is no point to use Redux at all except maybe for cool dev tools.
Redux Toolkit is really nice evolution from Redux.
Just for the amount of code reduced its worth the refactoring.
I haven't used it, but I'm assuming it reduces the amount of code you have to wrote, but increases the amount of code that is bundled in the app, is that correct?
RTK does add some code to the bundle size, yes, but:
It definitely reduces the amount of code I know that much from experience. The code is so much simpler and easier to read and its not the default way for building Redux apps.
Very nice explanation and example, tailored to both people not using Redux yet, or thinking about migrating to RTK :) Thanks for this article.
I have a complementary question though: how does RTK integrate with async middlewares such as Thunk? Nowadays, most React websites using Redux for state management also have async actions (API calls, mostly), which come with their own cumbersome stash of boilerplate (creating a pending/success/error action type every time, huge switch blocks in reducers, repeated testing for very similar actions, etc.)
I'm seriously thinking about migrating to RTK, but I don't quite see how this would integrate with our complex mix of async/non-async actions.
RTK already enables the thunk middleware by default:
and includes a
createAsyncThunk
utility that abstracts the typical "dispatch actions before and after the request" pattern:That docs page is effectively also the migration guide. Switch your store setup, then migrate one slice reducer at a time.
It also comes with a new "RTK Query" API that completely abstracts the entire data fetching and caching process, and can remove the need to write any thunks or caching reducers whatsoever if you choose to use it:
Thank you for all these details, I will check it out :D
You can throw a digital rock and hit a library aimed to make Redux eaiser.
The basic concept of Redux is fine. It did help a lot of people learn how to stop polluting their views with business logic. But I just feel all this keeps piling up more and more and more.
If you are only in React-land. Take a look at something like Hookstate
blog.logrocket.com/simplify-react-...
There is a lot I like about Mobx, but I sure don't like the fact that I'm wrapping everything in a higher order component. Clearly there are ways you can use hooks now to tie into the component lifecycle.
If we over-simplify, all we are doing is running a dirtry check on a object, and then sending the new state update to the components that are reading those key/value pairs (ideally) through a lifecycle/api trigger.
React Context... I must be missing something. I'm basically putting a JS lib in the context (my store, reducers, etc.. ) so I can use them in child components. Yet, I literally have a module system...
import { MyState } from 'my-state.ts'
As long as whatever state code I use triggers the React lifecycle properly, then I'm only updating the components I need. There must be some magic with Context that I'm missing that makes it so much better than just a regular JS import.The Hookstate lib above basically wraps your state object in a Proxy. When you get a value from it, that triggers a
get
accessor in the Proxy where the lib uses the React useState, useEffect (maybe some others) to do the actual component update. Attached via a subscriber to the hookstate. Honestly, even Hookstate could be replaced by RxJS pretty fact - it is the Proxy code in there that is super handy. Either you do something like that to auto-detect what key/values are being used in what components or you need to declare what key/values you want to subscribe to in your component explicitly.Maybe it is just me. I'd rather build a function that clearly does something
getUserData(userId)
and that results in a state update vsdispatch({ type: STATE_ACTIONS.getUserData, payload: userId })
My method doens't need thunk, sagas, etc... Yeah, I might call it in a useCallback(), but that's ok.Rematch is better in my opinion
me too
🤦♂️ and the classic: clic pay to read something opposite of which an article is. I'm sorry but it sucks the way people try get attention on the vlog writing totally different stuff around that I didn't even read all bullshit here
Okay but where does that headline explained ?
How redux is dead ?
I am not a React developer still I could benefit from this post, thanks for making it simpler to understand. Now I am more interested in React
Thank you so much for this article. Just what I needed.
:)
After reaching comment section, I have just realised that two developers also can fight to each other 😅. We should respect each other and use whatever you are comfortable in.
Thank you for the article! This feels a lot like vuex which makes writing code much cleaner.
Overmind is much, much better overmindjs.org/
try pinia or vuex
Great article, thank you.
Nice post! Can I ask you what software are you using to make that drawings? (the ones from esri). Thanks in advance!
Very well explained. Thanks
Why should i install a library that's about 10mb
When i can just install redux. redux-thunk and react-redux and the size won't still be half 10mb ? plus I'll still get my work done!!
It is 10kb. Including redux, reselect and immer. bundlephobia.com/package/@reduxjs/...
How to manage async operation in redux toolkit