What's new in Redux Toolkit 2.0?
There's a right way to write Redux in 2024. Core maintainer Mark Erikson will teach us what the recommended modern approach is, what's new in RTK 2.0, & the long road toward shipping 5 major OSS libraries simultaneously.
Links & Resources
Click to toggle the visibility of the transcript
Captions provided by White Coat Captioning (https://whitecoatcaptioning.com/). Communication Access Realtime Translation (CART) is provided in order to facilitate communication accessibility and may not be a totally verbatim record of the proceedings.
JASON: Hello, everyone, and welcome to another episode of Learn With Jason. Today on the show, we have Mark Erikson, as well as, I believe the rest of the core maintainers, we've got Ben Durrant and Lenz Weber Tronic. So, let me bring them up to the stage. Welcome, everybody. How are you?
MARK: Doing pretty good.
JASON: Super happy to have you all here. This is also fun for me, because, typically, we only do the one guest on the show, so having this more panel style discussion, I think, is going to be a blast. We already had good discussion going in the YouTube chat before we even went live. So, I am really looking forward to this one. So, I guess let me just start by doing a quick round of introductions. So, Mark, you've been on the show before, but why don't you kick us off, give us a bit of background.
JASON: Very cool. Lenz, how about you?
LENZ: Yeah, hi. My name's Lenz Weber Tronic. I'm joining from Germany, so I'm probably a little bit delayed sometimes, sorry about that. I work full time as a maintainer after polo client, which is like since one year my day job, and I'm on the Redux team since 2019, so for almost five years now. I've been doing a lot of work on Redux toolkit, and I'm the creator of RTK query.
JASON: Cool, Ben?
BEN: Hello. I'm Ben. My day job I recently joined a procurement company called Market Dojo and in my free time joined the maintainer team for Redux in March of last year, so I'm the new face, but I wrote a lot of features for the 2.0, so it was really exciting to get that out.
MARK: I think this is also, literally, Ben's first appearance in public anywhere. So, bit of a coming out party here.
JASON: Excellent, excellent. So is this how big is the Redux core maintainer team, is this everybody, or are there other maintainers?
MARK: This is effectively it as far as active maintainers. There are some other folks involved in some fashion in other discussions. Tim Door is also one of the other original maintainer. Quick history lesson, because that's what I do. Dave and Andrew created Redux in December of 2015. Dan got hired by Facebook to work on React in late 2015. I got involved in contributing to the Redux docs in early 2016. And by summer of that year, Dan was busy at Facebook working on React, so he messaged me and another guy named Tim Door, who had also been involved with React Router and told me and Tim, you know, here's the keys, have fun, this project is yours. So, Tim has been involved for a number of years. He's a little bit more of kind of a sit in the background and, you know, just kind of maintain maintain things kind of a guy. There's Nathan Berima, who has been the maintainer of the Redux dev tools for the last few years. Let's see... Matt Sekowski has done some work around RTK query, including some of the docs and examples. Let's see, anybody else I'm forgetting?
LENZ: Sharekzi has done a lot.
MARK: Yep, couple other users who have contributed to a number of, you know, docs and tweaks and examples. And, you know, there's been lots of other folks who have done contributions over the years, but those are kind of the folks who are, I guess, semi consistently involved.
LENZ: Yeah, there's a lot going on in the reselect right now, but the two of you have more contact there.
MARK: Yeah, there's another user named Aria, who sort of contributing to the reselect library a lot about four or five months ago. And actually did a lot of the stuff that came out with reselect version 5.
BEN: Yeah, and I've been recently working closely with Aria on updating Redux templates and so on, so that's been very good.
JASON: Very, very cool. So, the last time let's see, we talked about Redux when we did the Redux toolkit episode, which I think is the all time leader for views on my channel. That one has been wildly popular, because I think people have a lot of Redux in production and a lot of questions about what the right way to do it is. I think you came back later to talk about Redux again, and I'm very excited, because today, or I guess recently, you have the is 2.0 out or is it almost out?
MARK: Oh, it's out. It's out.
JASON: It's out.
MARK: I hit the button on that the morning of December 4th.
MARK: Literally, right before running off to the airport, which was both a brilliant and a stupid idea.
LENZ: He does it every time. It's always relief at the airport, fix the wrong releases in the plane.
BEN: There's always something.
MARK: One of the truisms I've learned about maintaining a library over the years is that it does not matter how many alphas and betas and release candidates you put out, you will only start to get real meaningful feedback after you release the final version, because only a handful of people are going to try out the prereleases. And that limits the number of scenarios that people are going to run into. And, so, you know, once it's actually live, more people are installing it, and there's more different usage scenarios going on. And now you find out about all the things that you missed during the development process.
LENZ: I think you also do that reverse psychology thing, you know, like if you're waiting for two hours for the food in the restaurant, the moment you go to the restroom, the food comes. It's like the same with bugs. The moment you're somewhere in the jungle and have a totally shitty Internet connection, that's the moment people find a bug and report it. So, you just do the release, and you get on the plane, and the bugs come in. If you did the release, we would not get the bugs for three weeks if you wouldn't go on to that plane.
MARK: So let's kind of actually talk about the development timeline. So, we shipped Redux Toolkit 1.9 in, I think, October of last year... oh, I guess October of 2022. And actually let's go back even further. Redux core came out in 2015. Redux Toolkit 1.0 came out in October 2019. Four years later. If you do the math, it's now been over eight years since Redux came out. That means that Redux Toolkit has been out half as long as the original Redux core. And yet a lot of people still don't even know it exists. But we had been on RTK 1.x minor releases for its whole lifetime. We have added lots of new features and functionality. But we were also limited by most of the original build setup and most of the original tooling constraints. And that included that, you know, we effectively still treated ie.11 as a valid usage target. So, we had to backwards compile all the build artifacts to es5 so they would work in ie.11. We're still using... like the package contained artifacts like there was both common JS and ESM files in there, but the way that they were defined meant that a lot of times the build tools wouldn't pick up the ESM files, or they wouldn't work in all environments, especially under node, with the model node ES module configuration. So, it worked, but it didn't work as well in all environments as we wanted to. So what really started this process was we wanted to modernize the build packaging. We wanted to drop ie.11 compatibility, we wanted to properly update the packaging configuration so that more environments like Node would correctly pick up ES modules as the default. We wanted to shrink bundle sizes. We accumulated a lot of options that we were ready to deprecate and remove. And we had a number of new features that we wanted to add. I started working on the packaging at the start of last year, it was something I was dreading the past couple years.
JASON: I will say that any time that I have to deal with common JS to ESM to backward compatibility, I... usually what I do is make an excuse to not do that work.
MARK: I approve that approach, yes. So, I'd seen how hard it was. I had accumulated like a couple hundred bookmarks of articles about how to try a bridge configuring this, and I knew it was going to be hard. And then I dove into it and I found out this is actually a lot harder, complicated, and stupider than I thought it was going to be. My first attempts didn't work out. Various errors with packaging changes. I had to back off and try to create a set of example applications that would run in our own CI on every published job, so that I could test the build packaging against, you know, different versions of Create React AP, Vite, next, Node, to hope it was working in all those environments. Slowly got something that I thought worked in most cases. Every time I thought I had things configured right, there was another tool, another environment, another edge case that didn't cooperate. I actually ended up writing a blog post in August that was both an info dump and a set of gripes about just how hard this whole process was. I actually did not expect many people to read that article or actually have any interest in it, and it turns out that basically every other library maintainer I've talked about said, yeah, I run into these same problems, I identify with this, thank you for sharing your experiences.
LENZ: Ran into them today, honestly. It's horrible, it's just horrible.
JASON: It's a rough transition period where I think actually, you know what, I was about to say something I don't know is true. I feel like we've largely decided we should consolidate on one standard, but as I was saying that, I don't think that's true. I think there are absolutely people that are not common JS for life.
JASON: Right, right. That's the game. You never get to just be 100% right as a maintainer. You're always going to break somebody's setup, you know, there's the whole XKCD comic about that. Okay, so, let's talk a little bit about I guess as levelling the let's reset expectations for everybody not super familiar with Redux. Redux itself, state management originally according toed React exclusively, it's no longer React exclusively.
MARK: It's never been React exclusive.
JASON: Interesting, okay.
MARK: But the core truly is UI agnostic and we've been careful to preserve that as a valid way to use it.
JASON: Got it. So, Redux itself, the original implementation, I used it in it would have been 2016. And what I felt and what I've seen is sort of the general consensus around Redux is that it's very powerful and it also has no rules. And because it has no rules, it has led to everybody and I think that's sort of I get the sense that's sort of a core React team ethos, we don't make rules, we just give you primitives and you can go do whatever you want. And I think that has been in the Redux community it seems that's been a source of frustration. Well, we do it this way, because this is what we saw recommended by Dan, or this is what we saw recommended by somebody else that we respect. But those start to branch and diverge and you end up with Thunks and Sagas and all these Redux ecosystem things that can make it really difficult to understand. Like what is Redux versus what is Redux ecosystem, and what's the right thing to do, am I making a good choice, is this maintainable. And I think that all of that is what led to Redux Toolkit. Am I correct in that assumption?
LENZ: Fun fact, if you go to NPM today and you search for Redux, you will get around 13,500 pictures that all
JASON: Holy crap.
LENZ: contain the word Redux in some way. Most of those are before Redux Toolkit. The point Redux Toolkit came out, people still started writing their wrapper extractions, but it slowed down massively. And I think I haven't updated the numbers, it was 13,000 five years ago, it's probably now 13,500. But those first years were wild. And everybody had their own little abstraction.
JASON: Right. I remember when I one of the big questions I had when I first started using Redux is I remember looking at it and it was like, okay, this isn't actually a library, this is like a pattern. Right. And I remember, okay, so I guess I'll write a package that implements this pattern for our project, and I think I didn't publish it, but, yeah, I had my own implementation of Redux and the project I was working on. And it really is kind of fascinating how empowering that can be to develop or who would like to be really up to the elbows in the source code. But for the vast majority of us, we just want to get work done, and I think that was why Redux Toolkit, for those of us who have used it, feels like such a breath of fresh air. So, at a high level, what is Redux Toolkit doing that is different from Redux original flavor?
MARK: Yeah, so, little bit more background on that kind of spectrum of abstraction stuff. As you said, the original Redux core was incredibly tiny. Dan wrote a shrunk down version that was, you know, Redux in 100 lines. You can actually write a valid working Redux store in about 25 lines of code. There's even example of that in the documentation. And, so, it is a pattern with a whole lot of assumptions about the right way to use it that are not enforced by the original source code. And, so, you know, because it was designed to be so much like plug in your own behavior to begin with, that's why like you said, you have all the different people in the ecosystem building their own little abstraction libraries and, you know, pieces on top of it. And on the one hand that was great, because we had this whole big explosion of, you know, add ons and tools and side effects libraries and everything else. But it also meant that every Redux app looked very, very different.
MARK: And over the years, we saw many of the same complaints and concerns and many, you know, ecosystem packages that were doing solving the same kinds of problems over and over and maybe solving them in similar ways. And, of course, the number one complaint about Redux over the years was boilerplate. Why do I have to have actions/to dos.js, and solutions/to dos.js, and constants, and const add underscore to do, and all this stuff. And part of the answer is you didn't have to have those. Those were conventions. But they were admittedly conventions that were shown in the docs, and even when you started doing your abstractions, there was still a lot of that extra kind of boilerplate code that was common to write. So, we invented RTK to solve a few problems. One was that we wanted to give users a consistent way to use Redux. We wanted to eliminate as much of that boilerplate as possible, and we wanted to build in tools and patterns that solved most of the situations that people were using Redux for, and basically remove the need to have most of those hundreds and thousands of ecosystem add ons. So, we were able to learn from the kinds of problems that people were running into and the solutions they had come up with and build kind of like best practices, built in approaches from what the ecosystem had developed over time.
JASON: Got it, got it.
LENZ: That's essentially also like the development that you see if you look at Redux Toolkit in general. The first version of Redux Toolkit attacked the first big problem, that I have that reducer I have to write, I have that reaction creator that I have to write and the action type that I have to write. And the action type is always going to be a string, and the action creator 99% of the time is a function that takes one argument and makes one argument. And then I have reducer, has a lot of handwritten update logic in it. What the first version of Redux Toolkit did was it gave people create slice, which implicitly internally the action creator thing and the action type thing, and it added to it so you could just mute it. So, that already shrunk your code a lot. And on the other hand you had configure store, which gave you a starting setup with configure middleware and dev tools, so you didn't have that question the first moment, oh, now I have to pick middleware for my project, but you could just go with what we gave you. And in most projects it was already enough. If you needed to add something, you could still add it, like if you had your favorite middleware thing. But you didn't need to. You had a starting point. And after that, we shipped that, we waited half a year, we looked at what people had really as their next big problem, and that was like handling of asynchronous logic, because that repeated itself again and again and again. And people always have this starting action and this pending, this finished action, and this error action. And handling the three cases and everything. And we gave people Thunks. So the point was we ship an abstraction over that, we ship a Thunk to you that does these things in a sensible way, because it was easy to get boxed into there. So, as a result, create a sync Thunk was born. Then we noticed people were working a lot with lists and inserting into lists and filtering lists and doing all that kind of stuff. So, Mark swiped the entity adapters from
MARK: Yep, yep, the Angular version of Redux.
LENZ: Yep. We've been looking at other libraries a lot. The next was
MARK: RTK query came out in 1.6. That was you. The listener middleware was 1.8. So, later on.
LENZ: That was like the time on Twitter when everyone was writing, ha ha, I deleted 10 million lines of Redux code because now I have React query. Oh, people have that much code for that logic? We should do something about that.
LENZ: So, we got that data fetching library in there. It took a lot of inspiration also from other libraries like Apollo, like the tech validation system is what Irkle does with type names. Inspiration from SWR, some things like the structure sharing, the query dust, the other big middleware out there was Redux Saga, which is really cool, but extremely complicated and most of the you're bringing a truck to a bike race if you're using Saga. Almost always not the right tool for the job and almost always too powerful, too big. So, we got the listener middleware in there, which if you don't need action channels, which is the only feature I can think of that's not an RTK in the listener middleware, then the listener middleware is like so much less code you don't need to learn what
MARK: Generator function, yield star
LENZ: All that weird you don't really need to use.
LENZ: At that point the work of 2.0 started and that's more the story Mark and Ben can tell, because I've been watching them mostly from the sidelines.
JASON: I guess to frame that 2.0 question, what are like the big improvements, or I guess what makes it a major release?
MARK: Breaking changes. That, literally, is it, the starting point.
JASON: Right, right.
JASON: I mean, it is definitely been that's been a common issue for me, is that somebody will do something like that in a minor not realizing that, you know, the next time I update my packages, everything blows up and I have to do the archaeology to figure out what just happened, why is this breaking on a minor, and it turns out that common JS, EJS, exports field, all that stuff is a chaos monkey in a project.
MARK: Yeah, which is why I was putting off working on that. So, that was the first big set of changes we wanted to make. We also had a number of deprecated options we wanted to remove. And those were the initial set of breaking changes. And we also ended up making a number of types changes, as well.
JASON: Got it. Okay.
LENZ: I just wanted to throw in the question, like, which big features do you have in a major release? It would be best if we wouldn't ask that. The thing about a minor release, in a minor you get the feature. In a major, if people do the major right, you get nothing new. You don't get any goodies. Because we're just breaking things and that's why we give you the major. But in reality, people expect that if you get a major, you get so much more than a minor that we can't go out and not put any new features into a major. So, there are going to be some in there. But in a perfect world, people will just be like, oh, a major I have to write, like read the change log and trust my code. Minor, I get new features, but that's not where we are.
JASON: Go ahead, Ben.
BEN: I think in our case, there were some features that were made possible by doing breaking changes. So, we had started making breaking changes and actually as a result of those, we could then add a new entry point for React specific features in the core for Redux Toolkit, you know, rather than anything specific for query. So, by doing those breaking changes, allow ourselves to make even more cool stuff.
JASON: Got it.
MARK: A lot of the complexity around doing a release like this is... like I'm a developer, I've led project teams at work before, you know, of various sizes, but a lot of doing this was me essentially putting on a project manager hat and deciding, like, what are we trying to do, what are our goals, what is the scope of this release, and, you know, trying to drive us forward to get this out the door. And, in fact, one of the biggest keys to even having gotten this out by December, RTK Query has gotten a ton of adoption. I don't have hard numbers on it, but I can tell you anecdotally, a very large percentage of our usage questions in the React channels and the issues filed in our repo, both bug complaints and feature requests and how do I do this questions, are about RTK Query at this point. So, closely, the Redux using community has eagerly adopted RTK Query. And we have, literally, probably at least 150, 100 to 150 open issues with requests about features and bug reports, plus a few specific threads that have gotten hundreds of comments asking for tweaks and changes. And one of my concerns as we got into September, really, was that some of these requests were going to have to involve breaking changes to RTK Query, except that we had not yet even had a chance to comb through the issues, make a list of what individual things people were requesting for RTK Query, have discussions about what we wanted to do, figure out which of these could be a breaking change and which ones could maybe wait until later. Part of the issue is it's been four years and we're just now releasing our first new major version. It could be another three or four years until we do another major version, so we have to cram in as many breaking changes as possible now, otherwise they'll never get out. But I could just see this giant undefined size and scope shape looming over us. And it was scaring me, and I was like it's going to take us at least another year to get the RTK Query stuff done, and that will delay the release of this, and it's just going to be hanging out there the entire time. And, finally, in late September, I was actually sitting in a plane on my way to a work trip, and inspiration struck me, and I messaged Ben and Lenz and I said what if we just cut scope and we defer everything about RTK Query until after 2.0? And if it's small features, fine, they can go in 2.x minor releases. And if it's breaking changes, we can be willing to do an RTK 3.0 maybe late next year that would have breaking changes just for RTK Query. But, like, in the interest of actually shipping this, let's just cut scope and defer all the RTK Query changes. They said yes, and at that point, now there was a relatively finite list of remaining work that needed to be done. Of course, that was crew, but it was a relatively small list of remaining things left. And, number one, I could feel the weight being lifted off my shoulders and my brain. And number two, now I could focus in and say, okay, let's fix these remaining issues, make these tweaks, do these docs updates, and now we can drive towards the finish line and actually get this out the door.
MARK: Playing project manager was tough.
JASON: I do think that is always a pretty unique challenge when you know what's possible and then you have to decide what's practical. And those are usually pretty painful decisions, because, you know, there's a lot that we could do, but there's never enough time to do it all. So, okay, I saw a question come in early on, and I want to make sure that we've got some time to actually write some code today. So, let me get this question up from Mateusz. Is Redux Toolkit able to handle large amounts of data sent from various websockets channels?
LENZ: Why not? That's probably the first question that I would have. Essentially, a Redux store is variable in your memory. And if you could have an array in your normal memory, then you can also have an array in your Redux Toolkit store. There might be something like every modification to that array will be an immutable copy of that array. And if you do that like 10,000 times a second, that might get slow. So, it depends maybe a little bit on how much traffic you get into the websocket channel, but it's like a few updates a second if you have only like 10,000 or 100,000 actions in there, that's perfectly fine.
JASON: Got it, got it.
JASON: Got it, got it. Okay. Another question came in that I actually this is probably a really topical question. Is Redux RTK compatible with React server components?
LENZ: Yeah, that's my question.
JASON: With such enthusiasm in your voice.
BEN: Loves it.
LENZ: I've been answering that so often. Okay, so, the thing about React server components is that React server components are stateless. And the thing about Redux is that Redux is a state management library. So, all you would have inside of a React server component would always be your initial state. And that's something that you probably don't want to have. And even if you were starting to manipulate that state, you would manipulate that state, you would pull out your HTML, and the next request you would start again at the initial state, because you should not get that state to grow, because if that state would grow, it would grow with all the users you had on your server, and you would mix data from different users, which is probably really not what you want. Then there's the other side, and that's the side of compliant components in React server components. And where you draw the line between those two. And for me and in my eyes, React server components are great for data that doesn't change a lot. Like that you render out of HTML, because you know during the lifetime of your application, you don't want to make that round trip to the server, or you don't need a round trip to the server, because that's not going to change anyways. So, if you're in a web shop, it would be the product information, the product picture, all of that. But if down there would be a comments section, that's much more dynamic. And that has to refresh a few times. That's something I would solve for the client component. And that's where at that point putting in Redux totally makes sense for client state in general, where also something like a data fetching library like RTK Query still totally makes sense, because you still have those interactions with some kind of API, just with more dynamic content. What's important there is that you draw the distinction that you don't have server data that's also part of the client data, because if you have something like a cart count or something that was rendered out as part of a React server component, when your clients have updates, that won't update. It would go out of sync. So, for each type of data, you would to decide is it server side data or client side data when you want to keep it. And then you get to the big problem in the room, when you have made all of those decisions, and that's server side rendering of client components. Because every client component that you write and that you write in the assumption that it's going to run in the browser has to run for the first page load on the server. And the worst case is that you do something like data fetching in a client component that renders on the server, and then you render it in the browser and doesn't get that state over and it has to do that same request again. And you have to do all of that again. So, essentially, what we need is a way to transport data from... that accumulates during that server side render into the browser. And there is currently no good way of doing that in general with React. There's no data fetching library out there that can do that, that can do that and reuse the data immediately. Without a lot of hacks. And those hacks at this point in time always go around and they use Next.js internals and not APIs that are provided by React, because React just doesn't provide the APIs that we would need. At this point we are waiting for the primitives to drop. I've patched React, I've added my own primitives and everything works, but it's probably not what the React team is going to add, because they always have a super complex plan for 200 years and before. And I don't have that plan, and I don't have those insights. The theoretically, it's possible to patch React to make it work to do all of that. Right now, you have to fall back on fallbacks like Next.js internals that are originally there to do stuff like transporting over CSS. And we don't want that. If we add support for streaming SSR, which is essentially that missing piece, then we want to do that with React specific APIs, and we don't want React Redux next package and React Redux remix package, and a React remix Redux package, but we want a React Redux package that's capable of doing that, and that's why you don't see that right now. Because even if each of those frameworks will give us an API to work with that, it will always be a different API, and we will have to ship a different package. And nobody wins if we play that game.
LENZ: We wait for the React team. We expect the React team to ship a version 19 of React soon, whatever that means for the React team. And we expect a minor after that to contain one of those primitives or React 20. But I'm more hoping for React 19.2 or something like that.
JASON: Got it, got it, got it, got it. Okay, great. So, I could probably ask questions for the remainder of our time, but I think it would be fun. Let's build an RTK project, right. So, let me take us over into this view here, and, so, before I get started, let me do a quick shout out. Thank you to our sponsors, Netlify and Vets Who Code, who have made captioning possible. We have Ashly here from White Coat Captioning. The captions are available at this link that's been scrolling at the bottom of the page. I'm still working on how to get those into multi streaming. I don't know how it works. But some day. Some day we'll get this to work. That... then takes us to we're talking about Redux, so let me drop a few links into the chat. Here is the... Redux site. And then we are talking to Mark. And I'm going to drop GitHub profiles today, because social media is a nightmare. Here's Mark's, here is Ben, and here is Lenz, all on GitHub. And I guess where should I start? If I want to spin up a new project, and I want to do like it's 2024, you're building a React project, you want to use Redux for state management, what's the right way to build a project today?
MARK: Well, the inevitable first question that I see popping up still every day on Twitter and React and Reddit, what tool am I using, Vite, Next, remix. For sake of simplicity, let's go with Vite. If you click on the getting started there in the header, we should have our current installation and setup instructions, I believe somewhere in here should point to a Vite template. Yep. The first line in that code block right there should be a line that you can copy/paste.
JASON: Got it, okay.
MARK: The npx degit Reduxjs.
JASON: We'll go up a layer here, then do one of these. And I'm going to call it... RTK 2.0. Can you do a dot?
JASON: Let's do that. Real fancy. And then once we're in here...
MARK: The first caveat is that the template that we're cloning right now is actually still going to reference RTK 1.9. We have some PRs open that will update all of our templates to 2.0. We have to do a little hand tweaking to update the packages. The core contents are still going to be basically the same.
BEN: Much in that template that suffers from breaking changes, for example.
JASON: Gotcha. Do this real quick to see what's going on. Probably still too small to read. I have my project, and this is a Vite project. It's got RTK, React Redux, the React core packages, and then it looks like testing stuff, type stuff, Vite itself, es lint, js DOM, prettier TypeScript. Okay, not a lot. Doesn't have that daunting feel of, oh, God, I have 15 packages that make this function. This is pretty straightforward.
MARK: So, the first thing we can do, and this should be a relatively small set of tweaks, get the packages. Change the Redux Toolkit version to just caret 2. And the React Redux version to caret 9. Install those... fingers crossed.
JASON: All right, did what we expected.
MARK: Okay, we can try running this and see if there are any obvious complaints.
JASON: All right, this is...
MARK: That's always a good first sign.
JASON: Here we go.
MARK: Which actually right there, that speaks to some of the a lot of what our goal was for 2.0, it's a mile long. There are a lot of total changes, there are a lot of things that could be considered breaking changes. But at the same time, a lot of our goal was that many projects ought to just be able to bump the versions, in many cases things would still just work. Of course, that's not going to be true for every project, and I actually tried out upgrading some, you know, some complex React Redux projects that I found on GitHub. But like the goal is even though the list of changes is long, in many cases it's it just works, ideally.
JASON: Yeah. I mean, this is... so, I'm sending a link here through to show kind of what the changes are. And you're right that this feels like a lot, but it also feels like this is to your credit, you all are one of the most thorough maintenance teams that I've seen. And verbose in a good way.
MARK: I have seen a number of people complain that we have, literally, too much documentation, and that can become confusing. And I'm sympathetic to that. We actually have three different doc sites, because there are three primary libraries, the core, RTK, and React Redux, each with their own repository. I've tossed around the idea of trying to merge them some time, but that would be a lot of work, so not doing that one any time soon. It's... I would rather err on the side of having too much information than not having enough information.
JASON: Yeah, and I think that is kind of always the classic challenges. How do you draw the line between need to know and don't need to know. And I've always leaned the same way, like it's better to have the information available than to make somebody ask for it, but then that information design is hard. How do you make sure the people discover what they need and when they need it. That's, literally, an entire field of study.
MARK: It is. I'm sure you've talked to Rachel Neighbors before. She is very passionate about the design of information and documentation.
JASON: Yes, absolutely.
MARK: If we scroll down slightly here in the doc page, let's actually briefly talk about a couple of these major changes here. So, there at the bottom of the intro section, I believe, it calls out a couple of the areas.
JASON: Okay, so, I was looking at kind of the headlines here. You want me to go to a specific...
MARK: Yeah, right there, right there.
MARK: Of all the changes that are listed as possibly breaking, we think these three are the ones that are most likely to cause potential issues for folks.
MARK: And that was a really cute trick early on. Unfortunately, TypeScript hates that, because it doesn't know that this implicit to string call is happening. It just sees, wait, you're trying to pass a function here, why are you doing that? You can't do that, it's not allowed.
JASON: As usual, TypeScript is the thief of joy. Continue.
MARK: In order to make it work, with TypeScript, you would have to say to do added dot type, because we also attach the string as a .type field on the function instance itself. So, that works, but then you run into another problem. If you look at the action field in the reducer, TypeScript has no idea what that variable contains. It sees that action is there, it doesn't know what the action.payload field is, it doesn't know in this case that it would be a to do object. There is no way for TypeScript to make that observation. This is not type safe at all. Lenz later came in and added an alternate syntax to create reducer and to the create slice.x reducers field that is more like a builder pattern. So, you get an object with a builder.add case method in it, it takes that action creator function as an argument, and it can extract the TypeScript types of the action object. TypeScript does know what the TS type is, and it connects all the dots for you.
BEN: One of the goals we've had very commonly is, you know, with Redux Toolkit is you annotate as few types as possible, essentially.
BEN: Everything is built. We write crazy, crazy types so you don't have to.
LENZ: I started the tradition and Ben has kept.
BEN: Happily adopting it.
JASON: Honestly, this is why... there are so many reasons that people should be nicer to open source maintainers, but truly the amount of effort put into wrestling TypeScript so that I ought in user land can do whatever I want and it just works whenever I plug it into your library. I've seen what that TypeScript looks like, and everybody should be buying you dinner all the time.
BEN: It's happened.
LENZ: Is it okay if I plug another show on here?
JASON: Of course.
LENZ: Okay. Mark and I were recently on an episode of a Michigan TypeScript, which you can find on YouTube. And we showcased those types. So, if you want to break your brain, these are one and a half hours that will break your brain. And I'm sorry, again, my Internet connection is horrible, so the live screen there is chopped up at some points, but it should still be very entertaining.
JASON: All good. Yeah, let me get this up on the screen here for a second, for anybody who wants to screenshot that and copy/paste it out of your photos, because that works on computers now. Way to go, technology. Okay. So, this is awesome. Let me get back to the migration docs. Do you want to cover any of the other pieces here?
MARK: What changed here is that up through RTK 1.9, both of these syntaxes on screen were valid. We had updated the docs maybe about a year ago to say that we preferred and recommended the second approach, the builder syntax, both of them still worked. Starting in RTK 1.9, we actually added a development mode warning so that if you are using the older object syntax, it would print one warning per app that says by the way, this will be removed in 2.0. And, so, in 2.0, we removed the object syntax and now the builder syntax is the only way to use those fields. We did actually put out a code mod that will automatically convert existing code from the object syntax to the builder syntax.
JASON: Beautiful. So, here are the code mods. Let me copy this link and drop it in here. For anybody that wants to get at that. Throw it up on the screen again.
BEN: The other thing with the builder syntax, it's actually more flexible because it supports more than just matching against the types. As well as our case, which does the same as the object syntax matching specific type. There's also add matcher, which takes a type predicate, so that can be any function that received the action and tells TypeScript what the action looks like and it can respond to that with the org reducer. There's also add a default case, which if none of your other reducers recognize this action, then run this default case, essentially.
JASON: Got it.
MARK: Overall, the object syntax piece is probably the biggest thing to cause issues with the migration process in terms of literal breaking changes to the code, but if you look at it, it's the same number of lines, handful of extra characters, but not like this is a really, really complicated set of changes to existing code.
JASON: There's a question, is the builder pattern tree shakeable?
LENZ: Neither of them is tree shakeable.
JASON: Okay, neither option is...
LENZ: In both cases you build one big object and one object can never be tree shaken or split up. You build one big blob, it's not tree shakeable.
JASON: Got it.
LENZ: Of course, if you build other module boundaries, those can tree shake, but within one call of create slice, nothing can ever tree shake. Literally impossible it would be able to.
MARK: Although on that note, maybe now would be a good time to talk about one of the interesting new features and this would be Ben's specialty. Redux has always had a function called combined reducers. The standard Redux pattern is technically at the end of the day, there is one giant reducer function that handles updating all of the state for every action, but for purposes for maintainability, we break that one big function up into individual smaller functions called slice reducers, based on the pieces of your state. So, if the top level pieces of your state for a blogging app are state.posts, state.users, state.comments, then you would have a post slice, comments slice, users slice, each has its own slice reducer, and each of those updates its own state in isolation. As far as it knows, there's nothing else to update at all. And then you combine those together with a little helper function that merges those into the one parent object. And, so, that's always been part of Redux. That's the standard way to use it. And configure store calls that combined reduces function internally. Now, something people have done over the years is they want to lazily add more slice reducers to the Redux store. And there's documented patterns for doing that, but it takes some manual effort. And there's been third party packages to help with that, but never been anything built into the Redux libraries themselves to help with that process of code splitting or lazily injecting reducers. So, maybe like a year and a half ago, Lenz just had a bit of a brainstorm and wrote down an idea for here's something that I think we could add that would help fill that case. And then Ben went and just built it over the course of like a couple weeks maybe earlier this year. So that ended up in RTK 2.0 as a new feature called combined slices. And that should be listed in the migration page.
LENZ: One little thing to adhere. There was always a problem with the pattern of injecting the reducer and that's that after you injected the reducer, the reducer didn't have state yet. So, you always injected a reducer and then you dispatched an action to trigger the reducer to have it create its first state. And that was a problem with React specifically. React component wanted to inject a reducer, because then you would immediately trigger another render by dispatching that action. That was causing a lot of bugs for people, and we didn't want that pattern. So, essentially, we were blocked adding something without finding a work around for that and finding a way of doing that without having to dispatch that action. And that's the main story and the main thing that we needed to unblock to get this working.
JASON: Got it.
MARK: Ben, this is your baby implementation wise. Tell us about it.
BEN: Yeah, yeah. Combine slice was based on the RFC that Lenz put out in October in 2022. I just kind of spent a lot of time getting attached and implementing it all. And because there was some really nice features that I really liked, other than I mean, as well as the injection, which is the main idea. But even just looking with the basic example, it's got some nice because it's with Redux Toolkit rather than Redux Core, it can be more opinionated. It can what it does is if you give it a slice or an API instance, it automatically mounts those under its reducer path. You don't have to build the whole object yourself. It will automatically just go, okay, this slice needs to go here. Then with the injection story if you scroll down a bit, I think there's an example of... yeah. So, basically, you call inject where the slice that you want to inject and that gets added and you get a new version of the reducer, which is the same reducer, but it's typed to know it's got that reducer injected, because by the time this inject call has happened, that reducer is now in the state. And the other thing that's not documented here, but it provides a selector API, because we're not dispatching an action when that reducer because we're not dispatching that action, the state doesn't appear, but we still want our selector to receive some sort of state. So, the selector API, you pass a normal selector to that, and if the state doesn't exist in your Redux store yet, it will call it with the reducer the slicer's initial state instead. It will go, okay, here's what the initial state would look like if it was there. It's not going to complain it can't find it because you're lazy loading and it hasn't appeared in state yet.
MARK: So, previously, you know, with kind of the earlier handwritten versions of lazing loading and reducers, your code might have needed to account for, well, is this actually added to the store or not. And now we kind of, you know, automatically fill this in to where you use one of the selectors and even if it isn't actually in the store yet, we still give you back the initial value and the rest of your code can say, well, the value will always exist and keep going.
BEN: That simplifies the TypeScript story, as well, you don't have to account for all those optionals and nulls and so on. It will always be there.
JASON: Nice, great, cool. Okay. Did we want to look at anything else, or should we dive into some code?
MARK: Let's see, maybe couple other things real fast. I think the next section down is adding selectors to create slice. You know, we've added a few more options to the create slice method over the years. And let's see...
JASON: Where was it? I was just looking at it.
MARK: Up a little further.
JASON: Here. Selectors.
MARK: Yep, yep. Ben, you want to cover this one, as well?
BEN: Sure. So, it's been wanted for a long time, because you define well, previously, in legacy you would separate them all by file, but part of the point of slice is saying these are all very similar linked fields, so what if we just all created them in the same place. So, by doing... by adding selectors to create slice, you're then for TypeScript, for example, it knows exactly what your state is going to look like, because it knows what the slice is going to look like. It can also receive different kind of selectors before. So, these selectors here are built to receive just the slice state. They are not normally, you'd write a selector function that receives the entire root state of your global store and it would have to go and drill down all the way through it that to find where that slice is and do whatever it wants with it. Whereas with these, because they are slice selectors, they are built to specifically just take that slice data and do what it wants. It doesn't have to worry about the entire root state. You then worry about your root state when you either call get selectors or use the preloaded .selectors, which assumes... and this is something that we specifically are pushing in 2.0, is that slices have a reducer path, which defaults to their name, which is used by combine slices, but also you could manually mount things under their reduced path. That's something that's come with RTK Query for a long time. That's had a reducer path function, and that reducer path property, and that's now carried over to slices, as well. Slices have an opinion where they think they should be in the state. And, therefore, we can use that information for slice.selectors, which is preloaded to go, okay, I'm going to look into the root state under my own reducer path so you don't even have to worry about giving me a selector to find myself, I know where I'm going to be. So, that's kind of built to simplify a lot of cases for you.
MARK: Part of the idea here is that, you know, each slice reducer is just a function. You could use it anywhere. You could use it with React's use reducer hook if you wanted to. Technically, you could use this as the entire root reducer of a Redux store.
MARK: But, again, like 95% of the time, I'm going to take that counter slice and you see where we've got the name counter field in there. I'm going to set that up, so that it acts as state.counter at the top level of my Redux store. So, why should I have to write that name counter in two, three, four different places? If we just sort of assume that you want to do the often standard thing as the default, we can reduce a little bit of that duplication and provide some better usage experience out of the box. Just assume that they are going to do the obvious thing, allow them to customize if necessary, but prefer the obvious default.
JASON: Got it. Got it. Okay, there's a question here that I think is relevant. So, I'm just going to ask it now before I forget. But Phil is saying I find myself writing selectors for every single field in a slice. And what's the recommended approach to avoid this?
MARK: The shorter answer is don't. There's actually a usage guide page in the Redux docs that talks about writing selectors, and it talks about what are selectors, what is memoization, when and why and how should I use selectors, what is the reselect library, and it's got a few extra tips and guidelines towards the bottom. If you scroll down under the using Redux section under logic and patterns...
JASON: Logic and patterns.
MARK: Yeah, driving data with selectors. Yep. And if you go to the table of contents section there at the top and expand it, one of the final items should be balance selector usage. And this is one of those kind of hazy things, you know, there's nothing preventing you from creating a unique selector for every single state.counter.x, state.counter.y, state.counter.the other thing, field, but there probably isn't a lot of value in doing that, right. You can, but you're writing a lot of code, and I'm not sure how much benefit it's going to have. One common mistake that I think people make, you don't you definitely don't need to have memoized selectors if the only thing it's doing is returning a value. Memoized selectors matter if you're deriving new references. To dos.map and returning the ID, or constructing some kind of a new object. If it's just a look up, then really the selector can be a basic function. You don't need the reselect library for that. And, like, in general, I would say, you know, write selectors to get maybe a couple important fields out of the slice, definitely write selectors if you are deriving new values, but generally don't feel like you need to write selectors that wrap every single field. There's no real benefit for that.
BEN: I think a good way to go about it would be instead of every time you add a new field, make a selector for that. It would be every time you need to select something out of state, make a select for that, because that may not be the same thing.
JASON: Got it.
LENZ: There might also be a little bit of a disconnect here in the question and Mark's answer, because Mark answered mostly about selector functions here. The question is also writing selectors in general. Even if you just write use selector and put a function in there, that's a selector, too. And, of course, you're going to write a bunch of those. And it can't always completely be avoided. The one big thing that I would say is usually your state should contain pretty raw data. And your selector should derive data. And your component should not select five or six values from your store, and then calculate something out of it inside the component and work with that. But a selector should already be doing that calculating that something that your component needs. And then you select that. And that way, of course, sometimes you get to the point where there's like a first name and a last name and an age and you want to select all three and you write three selectors, that's okay. But if it's something more complex where you would have a calculation that you would maybe put into a use memo in your component, that's too late. You shouldn't select five values and calculate a use memo over it. You should have one selector and that selector should already have made that calculation for you. So that might be another way to think about that for a little bit. But if you just need the blank values, then just having five selectors is totally fine. But they don't need to be memoized selectors. They can also be in line selectors. But that's totally fine then.
JASON: Got it, got it, got it.
BEN: It's worth noting that we do advise selectors should be as specific as possible, just because with your use later hook, when the selector changes, that will re render components, regardless of whether you're using how much of that state, you know. So, you make sure that the selectors that you write return as little as need, essentially, that the state.food or bar, you know, rather than just returning the entire state and then pulling out of that. You be as specific as what you need. So that may end up writing lots of selectors and that's okay.
JASON: Got it. So, to maybe put like a broad view on this, the goal with selectors and what we're trying to do is we're effectively trying to use selectors as the business logic layer in the application. So the Redux store is raw data, whatever we would, you know, pulling out of a database, we'd put it in the Redux store, and that's the raw stuff. What the current state of things is in the UI or whatever it is we're doing. And what I would historically have done by pulling some of that state and then writing a function inside my component to manipulate that state, that's what would go into a selector, so that what I'm doing is mostly my components are being what React components or any components are supposed to be, which is functions that return state, they don't do business logic. They not functions that return state. Functions that return UI, basically. You get some data, and you take that data, and then you show something on screen based on what data you received. You're not doing the business logic to manipulate data inside of these components.
LENZ: One thing that I maybe said too specific, reducer should not necessarily be your raw data. Reducers should be the most common data your application works with. So, if you have some kind of derivation that you would always make after that and you don't need that raw original value at all, then throw that original value away and have the pre calculation going on. The thing is that many people start duplicating data inside their reducer. Like I have five numbers in an array and I have a few called sum, and every time I add something, I have to update the array and the sum, and every teem I delete something, I have to update the array and the sum. And that means I duplicate logic and I run the risk that I forget it once and things run out of circuit. In this case, always just have that array in there, because it's like the common data for all derivation you want to do. And the summing up would be your selector.
LENZ: But if all if you're never interested in those original values in the first place and you ever only want to have that sum available, then by all means put that sum in the reducer and don't put extra logic in your selector. So, it really depends on what you do. If you're interested in the array, if you need to know which items are in there, what the length of the array is, et cetera, then put the array in there. If you're just interested in the result of the calculation, just put the result of the calculation. But don't do both, because it will run out of sync, because we are human and we write bugs.
BEN: Yeah, speaking from experience, I starting out using Redux would store so much derived data, because I wasn't aware of, you know, deriving in memoized selectors. Then having the epiphany, actually, all of this data I could just derive and make somewhere else, and that would be consistent and my state would be so much smaller by keeping all this data out, as well.
MARK: Yeah, the React docs, one of the nice things about the new React docs tutorials is they actually give very similar advice. Like the docs page about you might not need to use effect, I think, you know, lists a number of things you can do to avoid effect. One of the common things with use effect, I'm going to set state in an effect to try to apply updates coming from props or something like that. In a lot of cases in React, rather than trying to do a set state all the time, you can just calculate final values in the middle of render, whether or not it's in a use memo, you just take these values, add them together, based on the inputs, and so you're deriving the results as you go. So, with Redux and selectors it's kind of the same thing, you can pre calculate some of these values. And save them. Sometimes it makes sense to do that. But as a general principle, it makes sense to keep more of the original data in state and read and recalculate and derive the results as needed, so that it's consistent everywhere it gets used.
JASON: Got it, okay. All right. So, the time flew here, because we're basically at the end of our episode. So, we didn't get a lot of code written, but boy did we cover a lot of ground. And I'm actually completely okay with that, because I feel like this was, you know, being able to ask these questions directly to the people who are writing the patterns is always very helpful. So, for folks that want to get more, we've shared the usage, we've shared the migration. Where else should people go if they want to learn more, dig deeper?
MARK: Those would be the starting pages. The way we've got the doc sites split is that the Redux core docs have the documentation on actually the core library, which again people should not be... you don't have to install the literal Redux package yourself, that's already included in RTK. The core documentation site has the overall tutorials, and the usage guides that sort of pertain to anything that is like broadly Redux related. And then we have these separate sites for Redux Toolkit specifically with the API references and the discussions of how to use those specific API methods. We, all three of us, hang out in the Redux channel in the React Discord and we answer questions. Ben has been doing an awful lot of that lately. We also keep an eye on GitHub issues and discussions and, you know, some stuff on Twitter.
JASON: Got it, got it. We have one more question before I let you go. Is it still safe to use Redux persist?
BEN: Yeah, we think so. Obviously, there's the fact that it's the maintainer store is not really there's not really someone in the position of maintaining it, but it's functional. There isn't anything that's broken. There's a few challenges specifically using it with RTK 2 or Redux 5, there's a couple other things that need tweaks that are very simple to fix. We've got issues open for those.
MARK: TypeScript type compatibility mostly.
BEN: Exactly. But it's functional. It's widely used. I use it for a lot of things. It functions. There isn't anything that we're particularly worried about it. For the moment, I don't think it's something we intend to adopt ourselves, because we think it's we do enough. We think Redux persist is the current ecosystem and it's fine.
MARK: More recent library called Redux Remember that is an equivalent tool that would do the same kind of job.
BEN: Yep. Yep.
JASON: All right.
LENZ: Did we actually play with that, or do we only know it exists?
MARK: I mean, I haven't had a chance to use it myself. I've seen other people try it out, and it looks like it is being actively maintained. So, I get the sense that this is a valid alternative, but I can't speak to, you know, the results personally.
BEN: And as an overviewer, it looks very similar to Redux persist, so it shouldn't be too alien to anyone previously using persist.
JASON: Cool. All right. Well, thank you all so much for taking the time today. This was fantastic. To everybody watching, thanks for hanging out. Please, if you enjoy these shows, please take the time to like the show, subscribe, all those things. I know it's a shameless ask, but it really does help keep the show going. And if you want to get really wild, we've even got stuff that you can get early access to. Like content that's only available like a week before it goes public. So, if you want to support the show for real, get on here and do this thing. I'll send you a link to it. You can also sub on Twitch, you can join the Discord, you can do all sorts of stuff to help keep Learn With Jason going. So, thank you all very much. One more shout out to our captioner. We've got Ashly here from White Coat Captioning today. Thank you so much. That's made possible through the support of our sponsors, Netlify and Vets Who Code. So, thank you very much for making the show more accessible and possible. While you're checking out things on the site, make sure you take a look at the upcoming schedule, because it is just packed. There's so much good stuff. Next week we're going to have Jessica Janiuk on the show. If you saw Mark Texan last week, the week before last, Jessica crashed the show, it was very funny. And she's going to come back and talk about Defer, which is a feature she helped lead in the Angular codebase, along with some other features that are really making Angular exciting these days. We've got Adam Argyle coming here to the studio. We're going to shoot live in studio, it's going to be chaos I imagine, but a lot of fun. Make sure you are ready for that. And we've got Steve coming on to talk about Qwik and Astro. It's going to be an absolute blast. Lots more coming in, as well. Remember, get on the Discord, the newsletter, calendar, whatever you want, we got all the ways. Thank you all very much. Mark, Ben, Lenz, thank you so much for spending some time with us. We will see you all next time.
LENZ: Bye, everyone.
BEN: Thank you for having us.
Closed captioning and more are made possible by our sponsors: