Redux in 2023: What you need to know
Redux is still wildly popular. Mark Erikson is back to make sure we get the most from modern Redux. Learn common misconceptions, gotchas, best practices + patterns you might not need anymore direct from the maintainers!
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're bringing back Mark Erikson. Mark, how are you doing?
MARK: Doing pretty good.
JASON: Good, good. I'm super happy to have you back on the show. It is always an absolute pleasure to have the chance to talk to you. For folks who aren't familiar with you and your work, do you want to give us a background on who you are and what you do?
JASON: Yeah, yeah. So, Redux, actually, let's hold off for a second before we talk about that. We were talking a little bit before the show started, and we were reflecting kind of on the state of communities and how the React community is perceived an enormous community. Like there are thousands, if not millions, of people who have tried React and tens of thousands of us are shipping it to production, and all that leads to the perception that React must be this huge and well-maintained and thriving maintenance community. When you start kind of scratching at the surface and digging down, you realize that it's, like, you -- (laughing).
MARK: Well, I mean, lots of other people, but it's -- I mean, I've seen, you know, suggestions that for any given major website, you know, you've got 10% of people who actively participate, you know, 90% just read. 1%, who are like the core content creators, or like actively, actively involved. So, when you break down the numbers, it's always a very small percentage of people who are doing the work, whether the work is like just discussion or, you know, actively moderating, developing, building, contributing. Law of numbers.
JASON: Right, right. And, so, the reason that I was thinking about this is because you had mentioned that the React sub-Reddit, which in my mind is the bigger, more active ones, is down to a single moderator, which is you. And then how big is -- like in Redux, let's talk about Redux for a quick second. Redux is one of the early React ecosystem libraries, and it's also one of the most prolific by a pretty, like, healthy margin. It's in so many different projects. How many maintainers are there? Like core maintainers are there, on the Redux project?
MARK: Okay. So, let's actually go chronologically on that one.
MARK: So, Dan and Andrew created Redux in the summer of 2015, launched it 1.0 a couple months later. I got involved in the beginning of 2016 when I volunteered to write an FAQ page. Dan had gotten hired by Facebook to work on React in like November 2015. By summer 2016, so like just one year after Redux came out, Dan was very busy working on React, and, so, he effectively handed over the maintainer keys to myself and a guy named Tim Dorr, who was already very active working on React router alongside Michael Jackson and Ryan Forbes. At that point, there are basically two maintainers, myself and Tim. I've never met Tim. We've talked back and forth. Tim is very much like a let's leave this the way it is kind of a guy, you know, like a maintenance, maintenance maintainer, which is a totally fine point of view. So, I fell into being the person who is doing like the active discussion and trying to work on some releases.
JASON: Just arrived in the chat, by the way. Thank you, hello, Ben.
MARK: Nice, nice. At this point we've got, effectively, three -- well, two and a half active, active maintainers. Lens is very much involved, but he's busy with day job stuff working at Apollo and various personal life things. So, he's active in the discussions. He hasn't had much time to work on code lately. I've been doing a lot of the infrastructure work in the last few months. Ben has been doing a lot of the code changes recently. So, let's call it three active maintainers and another three folks who kind of hang around discuss and chip in. Oh, I should also mention... his handle is Methuselah, I know him, I can't think of his real name right now, but he maintains the Redux dev tools, and he's also done a number of types tweaks for both the Redux core and Redux toolkit over the last year or so to try to prepare us for an upgrade.
JASON: Cool. Yeah. So, I think something that strikes me is that for a package that has... hold on, let me just find NPM trends, and let's get Redux here for a second. And the Redux package is downloaded roughly 8.8 million times per day over the last month or so, according to this.
MARK: Day or month?
JASON: I don't know. How does this work? I'll just show this for a second. This is the trend, or the chart, I'm looking at. I don't know if that's daily downloads. Or average monthly.
MARK: Does that say year over there?
JASON: This says downloads over the past one year, but it has specific days. This is March 26, 9.3 million. I don't know if that's averaged over the last 30 days, or if that's per day. But either way, it's an enormous amount of downloads. I mean, let's add React here. So, Redux is downloaded roughly half as often as React. So, that's wild. That is really wild to think about, because for you to be able to give a full chronological history of everybody who maintains this project actively, and it boils down to roughly, what, eight or nine people through time, that's -- it really does just show how much of the tools that we work with are actively driven by a very small and dedicated group. You had mentioned that XKCD -- what is it, Nebraska.
MARK: Nebraska, yeah.
JASON: This one is a good, good little reminder of just how wild it is out there. And, yeah, so, I think this is -- let me share this trends chart, as well. It's really interesting to me how much of our code landscape that we would, you know, we just think, oh, well, Redux is just there. It just exists. But, no, it's actively maintained by a very small group. And you're all volunteers. Nobody gets paid to work on Redux. Is that correct? Or I guess you have an open collective or something?
MARK: Each of us has set up GitHub sponsors, but it's basically like coffee and dinner money, not like we're actually making, making money off this kind of a thing.
JASON: Uh-huh. You're not quitting your day job.
MARK: Definitely not. One, because I love working the a Replay, but also because, no, it's small donations, not like real job money at all.
JASON: Yeah, yeah. Anyways, this isn't... I realize I'm veering toward like lecturing on the importance of supporting open source, which I do think we should. We should be supporting open source more than we are, but I also think what you just said is also interesting, because you ran this chronology of 2015 to today of Redux and along the way were mentioning these big shifts that have happened. I know when I worked with Redux, I was definitely using like the 2015 version of Redux. I had, you know, a fairly, like, you know, the high boilerplate Redux with Thunks, Sagas, and the sort of big -- I think what has given people the impression that Redux is very complicated, and a lot of that comes down to this perception that, like, you need a lot of boilerplate, you need a lot of external plugins and dependencies to make Redux function the way that you need it to. But it's been going, what, seven, eight years now. And a lot has changed. So, you were on the show a little while back, and we talked about --
MARK: Two years.
JASON: God, two years. And that episode, actually, let me switch back to this view, because this was a fun little moment for me. We crossed... 100,000 views on this episode. Let me share this with the chat. And, so, two years ago we did this episode. And let me pause that. Open it up in YouTube here. 101,000 views.
JASON: This is the first video, the first episode, of Learn With Jason that crossed 100,000 views on YouTube. So, thank you to everybody who's watched it. Also, what a strong signal that Redux is very active in the landscape and very influential. Like, we're doing a lot with Redux out there in React land today. So, I brought that up, because the Redux landscape has shifted significantly. And not all of us have learned the new ways of using Redux. I think there's a lot of really popular educational material out there still using older approaches. There are, you know, courses and tutorials that were launched in 2016, 2017, that are still going strong teaching people Redux that's five, six years out of date now.
MARK: Or even worse... like relatively new tutorials. There was a one course extremely popular that kept advertising updated with hooks, updated with Redux or whatever, and it was still showing years-old patterns. Or for that matter, free code camps Redux section was the last time I looked still showing the original legacy patterns. I don't know if they finally got around to updating that. But it is very frustrating for us as maintainers to see people coming in with questions and it's clear that the learning resources they are looking at are years out of date. And either they are looking at something that was just very old and never updated, or they are looking at something that's new that's still not reflecting the patterns that we've been teaching as a default for three-plus years now. And we see this all the time, even today.
JASON: Okay. And that is why I wanted to have you on today, because the last time you were here about Redux, we talked about RTK, Redux Toolkit, and some of the modern methods, and I'll be honest, it kind of blew my mind how different it felt compared to the Redux I worked with in 2016 at IBM. Now, two years later, I want to talk about it again. RTK has been out for a little while now. There were big shifts. There was, I know, a move to update, even deprecate some core APIs, and that's caused a lot of discussion. Yes, your eyebrow goes up, because I got to see some of the discussion around that. Yeah, it is just wild to me how much the landscape has shifted. With that in mind, let's talk about it. Let's talk about what's new in Redux in 2023 and see if we can help maybe set the record straight for those who haven't had access to updated learning materials or courses, how should they be building today.
MARK: Sure. So, let's do the chronological thing again. That's how my brain works. I roll through things in sequence. We published RTK 1.0 in, I believe, October 2019. And I spent most of 2020 rewriting our core docs tutorials. I added this brand-new essentials tutorial, which, number one, teaches Redux Toolkit as the default standard way to use Redux. It's very focused on how to use it properly. So, walks through building an entire application step by step. And along the way, it shows each of the primary pieces that are included in Redux Toolkit, but it's positioned as, okay, now we're going to add this next feature to the application. Oh, look, it conveniently happens that the problem we need to solve is solved by this next piece in RTK. So, it shows you how to create a store, how to create slices, how to, you know, read data in the components, dispatch actions, normalize state, do data fetching. And then later on, I eventually added a couple more pages that showed how to use RTK query. I also rewrote kind of like the conceptual-level tutorial, so there's also a fundamentals tutorial that has no abstractions and starts from the bare basic principles and explains, you know, what is a reducer, what are actions, how would you use a basic store, how would you write some of this data-fetching code by hand. Shows how and why patterns like action creators and Thunks exist. And then finishes by showing and here's how Redux Toolkit simplifies all of those. So, those tutorials came out in 2020. And that has been a really big shift in how we've been able to teach and have people learn Redux. Late 2020 is when Lens started working on building this RTK query data fetching library, and so when I came on to do the video with you in spring of 2021, we had, like, an alpha or a beta of RTK query, and it worked, and we were in the process of shifting it from being a little stand alone temporary package, to merging it into the real Redux Toolkit package and getting it ready for release.
JASON: Got it.
MARK: So, we put that out in... I think like June 2021, Redux Toolkit 1.6. And we've put out three more minor releases of RTK since then. 1.7 had a bunch of new options for RTK query. We also... one of the longstanding complaints about Redux Toolkit and Create Slice was that in the case where you had two different slices that needed to depend on actions from each other, you could end up with like a circular import problem. One of the files would initialize first. The other field would not have initialized yet, and you would get undefined errors because of modular init problems. And Lens was able to come up with a very clever work around for this, where it only creates the reducer on demand as it's accessed, and that delays it long enough that it basically solved that circular import problem. I know that there were some folks who were afraid to use Redux Toolkit because of that concern. And we were able to solve it. Redux Toolkit 1.8 we shipped a new side effects middleware that we called listeners. First couple years of Redux, everybody was building their own side effect add-ons. Generator functions, RX JS, dozens of different add-on libraries. And eventually it settled down into Redux Saga, Redux Observable, and Redux Thunk. Basically, your choice of generators, RX JS, or plain functions. We built Redux Thunks into toolkit, but there were those using Saga and Observables which were kind of heavy and complicated, so we wanted a simpler way to write Reactive style logic but using async await as the base concept. Kind of like Thunks, but they can respond to an action that's been dispatched. And we shipped that in 1.8, and it's been very, very well received. Frankly, better than I thought it would be.
JASON: Always the best news.
JASON: Nice, okay, cool.
MARK: 2021 I took over as the maintainerer for the reselect memorization library. Updated type updates someone had done, added options to selectors things like cache size and comparisons. Then there's the one that we were alluding to a few minutes ago, which is in April of last year, I shipped Redux 4.2.0, where I officially deprecated the old style create store API and many people yelled at me because of this.
JASON: Yeah, I got a chance to see some of that discussion, and I lack the patience that you have. I've quit jobs over one or two annoying coworkers. I don't know how you are able to stand up to so much anonymous abuse from people saying, like, oh, your ideas are bad, or this is terrible, you're ruining everything. Just like this kind of stream of people being grumpy all the time.
MARK: Even just within the last two weeks, I was at the conference -- I was at the React Summit Conference last week, had an amazing day Thursday, and I got back to my hotel room, and someone had left a comment on one of those issue threads complaining about us deprecating create store. To be fair, this was at least the politest comment that anyone had left on that topic, and they actually rationally said here's why this bothers me and here's what I wish you'd done instead of. But it was yet another example where they had ignored the many, many reasons why I had explained all the, you know, purpose of this change. Had another couple complaints on -- it was either Reddit or an issue a day or two later. Literally, just last night someone popped into a pair of 2-year-old Reddit threads and left comments replying directly to me specifically insulting Redux and the Redux team and telling us how awful we are. Honestly, those I can deal with. It's the repeated complaints about deprecating create store that bother me. For a lot of reasons. One is people assume we don't know what we're doing, or accuse us of not knowing what we're doing. I can even understand people who disagree with it at the technical level. I don't want to use Ehmer for state updates, something like that, extra bundle sites. I disagree, but I can understand those points. It's the people that accuse us of doing it in bad faith. You just want people to use the library you wrote, not the one Dan wrote. Dan only maintained this library for one year. I have been doing it for eight years. I published all the last umpteen releases of all our libraries. Do I not have the right to say how this should be used?
JASON: Right. Yeah, I think that's sort of the, you know, the -- there's a fascinating and somewhat unhealthy relationship with hero worship in the React community that leads to, I don't know, it's very odd to me that there are some folks who sort of have blessed opinions, they can say whatever they want, and everyone is like, yes, that's correct. And the fallout of that is exactly what you're talking about. If a maintainer who's very well known says a thing in 2015, time will pass, opinions will change, but that maintainer is not there anymore, and so people will act like that maintainer still believes the thing, even if they just aren't working on that thing anymore. And it leads to very, like, dogmatic arguments instead of pragmatic arguments. And, ultimately, what we need to be striving for as a community is pragmatism. We're all trying to get work done, websites up on to the Internet, in the least painful, most maintainable, least likely to break way. And to say you've changed this in a way that goes against this old statement is not really like a pragmatic way of looking at it. You've changed this in a way that broke my workflow in X, Y, Z, that's a conversation we can have, because it's based in facts and goals and what we're trying to accomplish. You broke it because you disagreed with somebody ten years ago, that doesn't feel like an argument we can really have productively.
MARK: Yeah, I have two thoughts on this topic, I'll try to keep them short. One is that libraries and tools and needs and use cases evolve. Look at React itself. The React that came out in 2013 is drastically different than the React that we have today. The core concepts are the same. You're rating components that have props and state and re-render, but the syntax and the usage patterns are drastically different, and that's because people have used the library in different ways, people have expressed the needs to do things that were not originally anticipated. The core team has a vision for how it should be used and is trying to both respond to user feedback and evolve the library to solve what they think are the important problems. And Redux is the same way. The original vision and idea is, that Dan and Andrew had in 2013, are still the core of Redux. Single store, immutable updates in response to mismatched actions and selecting data in components, but the syntax and the approaches and the pieces that we've built are directly designed to solve the problems that people ran into with Redux and the apps that they were trying to build with Redux. The flip side of this is, and this is going to the hero worship point, and I want to be very careful in how I say this, because I'm not trying to critique or lash out at anybody. Everybody on the React core team has at some point publicly stated in the last few years how much they dislike Redux or think it's outdated or should not be used. Andrew, Dan, Brian Vonn, Sebastian to some extent. And they are absolutely entitled to have their opinions, they are allowed to state it publicly. I am not trying to say they can't have those opinions or state them. At the same time, it makes my job very, very difficult, because they are very visible, they have very high profiles. Everybody sees those comments, and they ricochet and fly around everywhere. They get a ton of responses, and people cite them for years. You know, there are specific tweets I can think of from each of those people that still get thrown at us or used in Reddit comments where someone is like, well, Andrew once said that Redux is awful. Or, you know, Dan said in that mock interview he did with a YouTuber a couple years ago that he would almost never use Redux. Or Dan once tweeted that he looks back at Redux code and finds it confusing. People cite these endlessly. And, again, that is -- it was a valid thing for Dan to say. But just because Dan had an opinion and said something once does not mean that nobody should ever, ever consider using this tool.
MARK: People don't think for themselves.
JASON: I think, Mark, I'm listening to you say this, and I'm realizing that I'm more petty than I thought I was, because if somebody came in and told me you're ruining the library that Dan wrote, I would just post the tweet of Dan saying not to use it. Be like, there's your answer. (Laughing). Bye! Lock the thread.
MARK: All right, all right. We've hit one of my own hot buttons enough. Let's move on to more productive discussions.
JASON: Good call, good call. No, yeah, so, let's talk a little bit about... so, one of the things that I found -- actually, I feel like you had a list that you wanted to go through, so I want to hand that back to you, because I want to make sure we have time.
MARK: Okay. So, let's talk about migrating to Redux Toolkit for a few minutes.
MARK: So, early on, the only real documentation for doing this was the last page in the fundamentals tutorial, which does explicitly show here's the app we've been building up using the old handwritten style patterns, and now we can migrate it to Redux Toolkit step by step. So, I did actually add a new usage guide page about six months ago that is explicitly instructions on how to migrate. So, it talks about, like, for example, old-style code and new-style Redux Toolkit code can coexist. You can do this migration very incrementally, piece by piece. The normal process that we recommend is start by switching over the store setup to use configure store. It's a one-time thing. And it will simplify that one file, you know, probably knock off 20 or 30 lines of copy, paste, setup, but it also adds the development mode checks for things like, you know, accidentally mutating state, or putting non-serializable values in the store. So, it's a good place to start with the process. And all your old existing code still works just fine. It doesn't matter whether you wrote the reducers as a bunch of switch statements or create slice. It's all just Redux code, all still works. Plug that in, then just continue doing one of these at a time, piece by piece. This page goes through and says each of the kinds of logic you can have in an application, here's how you can put that to a modern looking code with createSlice, or RTK query for data fetching, or switching to RTK listener middleware. I hope this is a useful guide that will help people do this. And I've migrated two or three different Redux projects to, you know, use RTK myself. In fact, this is what I spent most of 2022 doing at Replay. So, I can very much vouch that this is entirely feasible. We've also got an introduction section docs page called "Why RTK is How To Use Redux Today" and goes through what were the problems, why did we create RTK, why should you be using this. So, it's more of the background information on this.
JASON: Got it. Yeah. And, so, this is -- actually, maybe you've kind of touched on it, but if you could sum it up in a couple of sentences, why is RTK the way to use Redux today?
MARK: Multiple reasons. So, it was designed to simplify and standardize basically all the common usage patterns that people were doing already with Redux. What do you do with Redux? You write reducers that respond to actions and create immutably updated state. They need to care about an action type string. They usually have an action creator function that you use to do the dispatching. CreateSlice does all that by letting you write it without switch statements. Using simpler immutable update, and it generates your action creators for free. You typically did data fetching by writing a thunk that would dispatch actions before and after the request. Create async thunk makes all those actions, does the dispatching for you. You just make the request and return the value. Or even better, RTK query, hey, I want to fetch get Pokemon, it gives you the hooks and manages the cache life cycle so the data gets removed when your components no longer need it. People were writing Reactive logic with Sagas and Observables, but it was very complicated. So we have the listener middleware that lets you do the same thing with simpler syntax, smaller bundle size, and better TS support. I think standardization is an important thing. Up through 2018, I was maintaining a list of all the Redux-related packages and add-ons that I saw in the ecosystem. And there were, literally, thousands of them. And many of them duplicate of each other. And that's not counting the home grown abstractions that people had in their own repositories at work. Every Redux project ended up looking somewhat different. By providing a standard set of tools and opinionated recommendations on, you know, use a feature folder structure, put your logic in the slice files, it really helps making these projects, hopefully, look and feel a lot more common so you can go between them and actually have a better idea where everything lives and how it works.
JASON: You know, this is something that I've noticed as I've kind of gone through my career, is there tends to be this kind of big divide between providing a library that is only primitives and saying you're smart, you'll figure it out, and providing a library that says, look, I'm going to give you a few rules, and the rules are going to mean you can exchange ideas more readily, because things look similar and are somewhat conforming to a spec, right. And I've noticed that React in general tends to lean -- like React the core team, tends to lean primitives. They hand you a tool and they say this is really powerful. We don't care how you use it. They are kind of anti-file structure, anti, you know, just put it all in one file. They are not into telling you how to organize things. Like I said, that's a philosophy. And you felt that in early Redux days. It was very much like here's the boilerplate to make this thing work, and it's four files, and do whatever you want after that. It's just going to be whatever it is you need it to be. I experienced that very much when I was at IBM, because we had Redux in production, but I've never seen a Redux implementation that looks like that Redux implementation. Like, everything about it was bespoke, there were tons of little things we had hand rolled and we had Thunks, but we wrapped those in something else, so they weren't really Thunks anymore. You could kind of see, well, we're not quite sure what the convention is for this, so we're just going to make something up. Then it's no longer a thing that's, like, I actually think this is the root of why people struggle with Redux sometimes, is because their first experience with it is it had no resemblance of any Redux they've ever seen. I think the same thing happened with Webpack. Webpack is like here's these primitives, build plugins, and everybody built whatever they wanted, they were very bespoke, idiosyncratic. Then we all wound up saying, oh, Webpack is hard. But it's not. It was the ecosystem stuff because there weren't any rules that made it really hard. I think we're seeing a little shift away from that with, like, it folder-based routing, frameworks kind of imposing these rules, right. You must put files here, they need this use client or use server, you need to add this suffix or this prefix or whatever it is to make this thing function, and that gives us predictability, rules, structure, and now when you move between projects, you kind of know what to expect. When you open up a remix project or Svelte project, you kind of know where things are, unless someone goes out of the way to write custom config that changes folder structures and stuff. I'm seeing less of that now, because conventions make sense, makes things portable and more easy. All of that coming back around to say it feels like that's what's happening in Redux with the Redux Toolkit setup, is we're applying all of the knowledge of the last seven, eight years, and saying if you do it like this, you're going to get the best results. And, yes, we realize that's going to cause you to maybe move some of your cheese and change some of the ways you name things or where you want to put files, but if you do it like this, Redux projects will look like Redux projects, and they'll be portable, and you can share knowledge and code, because it all looks and works the same.
MARK: Yeah. The elevator version of all that is RTK takes all the things that you've normally done with Redux, we provide APIs that simplify most of those patterns, help you avoid common mistakes one way or another, and it's like all the stuff we've learned about how to use Redux built in.
JASON: Uh-huh, yeah. Well, cool. So, I think that makes a lot of sense. So, I am definitely, I think, somebody who would have maybe five, ten years ago, said don't give me rules, quit telling me where to put my files, and I'm very much about-faced on that. I really am a big fan now of convention. There's a line, obviously. I've used some projects where you have to create five files to create one new thing. That's a little too much convention for me, but I like the idea of pages go here. Utilities go here. You know, data fetching goes here. If I know where things are, then when I get a new project, I can go look and see how things are done, and I don't have to make my own mental map. I already know how the project is constructed. So, yeah, that's great. I am very onboard with that. Did you have any other bullet points on this, or did you want to move on to the next point?
JASON: Yeah, that was like the whole mystery of Redux, is you would ask how do I use Redux, and everybody was like Redux is a pattern more than a library. Like early days, right. You got told, set this boilerplate up, then after that, you're on your own. When we say we deprecate createStore, we wanted you to use configureStore from RTK instead, but it's also the whole Redux Toolkit library we want you to look at, become aware of, and start using. And all I did here was I added a JSDoc@deprecated attribute. And the result is when you import this into your editor, there's now just a little visual strike-through on that import. And the goal is that a beginner would look at that, be confused, hover over it, and see the, you know, the doc tool tip that says createStore is deprecated, we want you to use Redux Toolkit. Here's a link to that documentation page explaining more details. Now, you'll note, I did not add a run time warning. I did not break anything. And, in fact, in the upcoming Redux Version 5, I would be within my rights in a major version to, literally, remove createStore as a method entirely. That's a breaking change. I can do those in major versions, right. No, we did not do any of those. We are not breaking anything. We are not putting out warnings. We are just trying to notify people that Redux Toolkit exists. Now, if they still choose to keep using old school Redux by hand, that's their choice. But at least we have made them aware that it exists. And given --
JASON: I saw that you even introduced a way to turn off the deprecation notice by explicitly opting in to using the deprecated behavior.
MARK: Yeah, if you scroll down here, it says there are three options. Option one, you can just ignore it, it's a strike-through. Right below that screenshot. Option one, you can ignore it. It's just a visual, it does nothing. Option two, you can switch to the aliased import, which does not have the deprecated tag. It's, literally, the exact same function, just different Java doc, or you can actually switch to Redux Toolkit. And it's been effective. I've seen a number of people say, oh, I looked at this and I followed the instructions and I updated. It also still continues to be an annoying source of controversy. There's, in addition to, you know, comments on our issues and whatnot, there's like one specific stack overflow thread, like the first person who asked about this after I published the release, I replied and explained the reasoning and linked to the docs. And if you look at the comments in response to my answer, just every couple months someone else comes in and yells at me again and again and again.
JASON: So much patience that I lack. I would have -- I would be bringing strong "per my last email" energy to every one of those replies.
MARK: There is a bit of that. If you look at the last comment that I wrote in one of the GitHub discussions last week, I wrote a very, very long, detailed response, because that's what I do. Basically, reiterating most of the points I made in here.
JASON: I think this is something commendable, because, first, when you are convinced that you're making the right call and you're the maintainer of a library, that is the decision that you can make as the core maintainers and if the majority of the community is in favor of it, you can say this is the way things are. You can pin to an old version if you don't want this change. And the fact that you're willing to not do that, that you're willing to continue to release this API, that you're giving an opt out with legacy createStore, that you're only deprecating and not throwing runtime noticing or ESLint errors or anything that would be a blocker to people, that to me shows a level of consideration that's often not present in software. A lot of open source feels more like, hey, it's free software, you have to deal with whatever you have to do here, and this seems more considerate and thoughtful than that.
MARK: Backwards compatibility and literally maintaining the ecosystem are some of my highest concerns as a maintainer. Because it's such a widely used set of packages. Every decision I make has to take into account how is this going to affect users, how much churn is there going to be, is it worth any of that. And we'll talk about the new upcoming majors here in a minute and those same factors go in. They are major versions. We could make breaking changes, we are making breaking changes, but even for those, how much of an impact is it going to be? How hard is it going to be for users to update? Is it worth the effort that we're going to put people through?
MARK: So, in this case, even after we publish Redux Version 5, people are still going to be looking at outdated tutorials, still trying to input createStore. If I remove it, those people would truly, truly break and not be able to follow tutorials. People using older codebases or don't want to use RTK, I would break them. There are people like Redux Toolkit on the Redux core and create store, yeah, like a number of people suggested we could deprecate the stand alone Redux package, shift it over to be a new@Redux JS core package or something like that. Even that would involve a lot of churn. Have to update documentation, would be spitting out a replication warning for the entire package every time it gets installed. Is that worth it for users, and my answer at this point is no it's not. I'm trying to do the smallest thing I could possibly do to nudge people in the right direction without being a jerk and disrupting people's workflows.
JASON: That's right. That's a lesson we can all take home, right. There's a way to have strong opinions about software and to do our part in pushing it in the direction that we think is best for developers in the future of the web. And you could do that without being a jerk, without being pushy, without being rude. Like this is the energy that we should be bringing to these discussions, because we can feel things, we can believe things, we can push for things, but shouldn't be doing that at the expense of other people. Breaking the ecosystem to prove that you're right, or breaking the ecosystem because people are being mean to you. It will probably feel good for a second, but it will feel bad -- because then you really get yelling. People getting upset about moving their cheese is one thing. People getting upset because their project just broke at work, oh, that's not a fun day.
MARK: So, on that note, let's flip over and talk about the future, and if we have time at the end, we can talk about the ongoing debate and comparison with other points.
MARK: For the last six months we've been working on the next major versions of all of the Redux packages. We've been doing alphas for the first half of the year and last week, literally, while I was at the airport on the way to React Summit, I published betas for core and Redux Toolkit. So, let's look at these briefly. We actually converted the Redux core to TypeScript in 2019. Then it sat there for four years. Version 4 worked fine. Again, we were concerned about churn with major versions and even just updating peer depths and everything, you know, could have been an issue. As well as, okay, we update -- we migrated to TypeScript, did we accidentally break types, are there types changes we should make, this all really needs to be a major version. So, version 5 will have -- it's written in TypeScript, so the types are generated from that. We do have a lot of tweaks to the types. For example, the types used to default to what was referred to an any action time, it's a TS type that says this is an action object with a type string, but any other possible field is just any. We have no idea what's in this. That made it simple to use, and all that existed long before the unknown keyword is added to the TypeScript language, but it also made it very unsafe to use as a type safety perspective. We have this unknown action type where it says any field is considered to be unknown, so you need to do like a type guard check to verify, oh, it's really a to do added action before you can safely access like the pay load field or the may that field or something like that, so that you know what the correct type is. Similarly, the middleware type definition is said things being passed through are unknown by default instead of any. The other really big thing with all of the packages is the file format and ESM compatibility work. So, we shipped a mixture of common JS and ES module files for years. But the way you're supposed to define what your package exports has changed in the last five years. It used to be you provided top-level keywords called module for ESM-like file, or main for a common JS-like file. Now you're supposed to provide a top-level exports keyword that has nested fields that can say for this entry point, here's the types, the ESM file, common JS file, maybe something else. That's actually needed in order for those to work correctly as ESM under node.
JASON: You wrote this up, right?
MARK: I wrote 4,000 words for a blog post on the way back from React Summit, still need to write the other half of that.
JASON: Got ya. Make sure you're on the look out with that if you follow packages, because I followed some of this discussion of you and a bunch of other maintainers just trying to untangle the mess of how do you export properly when you're setting up your package.JSON, and I got lost. And I need that post, as well, because I don't know how to export packages in a way that's actually compatible.
MARK: I've been begging for years for someone who actually knows what they are doing to write like a comprehensive, authoritative guide on how to publish packages correctly in 2022, '23, '24, et cetera. The couple people I would really like to do this are Jason Miller from Google and Preact, or Matush, who works on a whole bunch of different packages. They are both experts in the field of publishing packages and file formats and all that kind of stuff.
JASON: What's their --
MARK: Matuosh's handle is andarist. There you go. Named after a fictional character, I believe.
JASON: I was not even close on that spelling, geez, my apologies. And then, yeah, this is Jason Miller here.
MARK: I've been specifically asking them to write an authoritative post. Pops up in tweets and Reddits to tell you how you're doing it wrong. I have found where people tried to put up guides. They are somewhat useful but not the comprehensive thing I've been picturing in my head. I will end up linking the resources I found as part of the post that I'm writing. I still feel like I don't actually know what I'm doing here, and it's a semi-imposter syndrome, semi-humility kind of thing. I feel like I'm the 3% that know what they are doing, but I still feel I don't.
JASON: That's got to be the terrifying truth of all of this. All the people at the bleeding edge of this industry are, literally, making it up as they go along. We're all like, clearly, they know what they are doing.
MARK: Yeah. I'm very, very carefully phrasing this blog post as "this is not an authoritative guide. This is a recap of what I've done and what I've tried and where I ended up. And I think it's useful, but please don't take this as like the defining set of things to do." Anyway, so the point is, I've managed to settle on a set of configurations that I'm now using in all of our packages, which I believe works correctly to say here's the ESM files, the common JS files and seems to work in Webpack 4, Webpack 5, node with ESBuild, et cetera. We've also modernized the JS output. No longer transpiling things for IE-11. If you need something else, it's your job to transpile from node modules for whatever your application needs. We are dropping UMD build files. I think these were primarily used as script tags for code pens and stuff like that. I don't have any real usage stats on this, but for the flip side, I am including a new file in each package that is an ES module with all the, like, development production checks filed away and modified to production mode, so you should be able to use it as a script type equals module ES module in the browser for that same use case if you want to.
JASON: That's got full compatibility with any modern browser?
MARK: Hopefully, hopefully.
JASON: Your toes were off screen, but we have to assume those were crossed, too.
MARK: And then the rest of this is like relatively minor changes that are in theory breaking, but hopefully not. For example, we now require that the action.type field must be a string. 99.98% of the time it has in any practical application, and it definitely is if you're using Redux Toolkit. It was always an assumption we made. It was always something we encouraged people to do, but technically you could have used like a number or a symbol or something. And given that we think it's the right choice, especially because this is what gets shown in the Redux dev tools history list. Let's just make that the actual requirement.
MARK: So, if you flip over to Redux Toolkit 2.0 beta release notes --
JASON: Which was up here. Wait, where was it?
MARK: Go back, did I have a bad link there? Maybe I had a bad link.
JASON: That links to the 5.0 beta. Okay.
MARK: That's my bad. Need to fix that link. Toolkit, releases on the right, 2.0 beta should be the most recent.
JASON: There it is.
MARK: This has the same file format changes and some various TypeScript tweaks. We do have a number of... let me find my own release notes here. 2.0 beta. We do have other similar type tweaks, like we've improved some of the inference. If you're trying to provide like some custom middleware enhancers, you have to be more careful how you provide them so the types get inferred correctly. We have removed some deprecated syntax and options. The biggest one is that originally, you could provide some reducers as like an object, where the keys were type strings. Like to do/to do add it, or passing in one of the action creator functions as the key. And it worked and was cute and nice for early adoption, but it works really badly for TypeScript. TypeScript has no idea was the TS type of the action object is supposed to be. So, we later came up with a builder callback syntax that does correct type inference, and we've been encouraging that is the default for a couple years. So we marked it as deprecated in 1.9. We did add a runtime warning say this is going away, and even provided a code mod to change it. So, we did actually rip that out into 2.0 along with some various other fields. Most of the same build changes. We also have some new features that are coming in 2.0. One is we've never had anything built in to help with lazy loading reducers for code splitting. Say you've got enterprise style application, only want to load the admin page stuff or the editing tab or whatever. You don't want that logic to exist in the store until they add it. There was always like a copy/paste recipe you could use to inject those later, but you had to do all the work yourself. They require keeping around references to all the old reducers, because then you had to recombine them, plus the new one, to make the new root reducer and then swap it out. And there were some other ecosystem packages to help with it, but they are all dead and unmaintained at this point. So, a year ago, Lens put out an idea of a hypothetical API that might kind of help with this, and then Ben, Eskimo Joe, sat down and wrote this out two or three months ago, like whipped it up. To be honest, I haven't had a chance to play with it myself when I wrote the release notes for this in one of the earlier alphas. I was like having to read the PR and go off the tests to try to figure out how to describe it. But the idea is, number one, you pass in the entire slice object, not just the reducer function. It assumes that slice.name is going to be like the top-level state key. So, if you say name to dos, it's going to assume that's going to be added as state.to dos. Which was, again, like the 95% default that most people did anyway. Technically, you could put it any way you want to, but most people are going to use the same key in both places. But the trick is, it uses some proxy magic inside, so that you don't have to swap out the reducer itself. You just insert the reducer once, and then you can inject more slice reducers into that top-level reducer, and it just works. You don't have to worry about keeping the references to the old one or worry about cases was that data loaded or not when I try to use it in a use selector. It intelligently defaults back to the default value in case it hasn't been added to the store yet. So, it -- we don't really have good examples of this being used in practice yet, because this is just now in beta, but we think it will help solve, again, another thing that people have been doing in the ecosystem, but there was never anything built in. We also have expanded createSlice to do a couple things that a lot of people have asked for. If you were using the create async thunk API, you always had to define those outside of the slice. Then you handled the reactions in the extra reducer field, because that's how slice handled reactions defined outside of itself. And it worked. And there were good reasons for it, but people had always asked us can we define Thunks inside of createSlice, it will be shorter, it will be simpler, I don't have to like supply the initial string prefix, et cetera. And we wanted to do that. The problem is we couldn't get the TypeScript types to work. Because you're supposed to get your root state type from the store. The store gets its type from the slices. But Thunks can access the root state, so it needs to know that, then you get the TypeScript circular inference problem and it throws errors. And we care about shipping good TypeScript behavior, and we weren't willing to ship a solution that would work badly for TS users. We get the code to run, but couldn't figure out a good way to figure out the types, so we kept punting on it. We finally settled on a sort of compromised solution a couple months ago. The reducers field, which has always been an object inside of createSlice, can now also be one of those builder callback things, similar to what we did with RTK query. You're still going to return an object that will have fields to do added, to do toggle, et cetera, but you use this builder to make the values for each thing. So, you call create.reducer, create.prepared reducer, or now create.async thunk to say what is the thing at each of those fields. And if you're making a thunk inside of there, the syntax is a little simpler. It assumes some of the types and stuff by default. And I think most of the time people aren't doing really advanced things with Thunks in those cases, so our answer is, like, really need to access get state inside of there, you're going to have to do a manual cast to root state, and that seems to work around the compile problems. Worse case, you can still define a thunk outside and do it the way you have been. But if you really want to do it, we've provided something that's hopefully a little bit simpler. And, again, we think that RTK query solves most of the things you would need to write Thunks for anyway, but it's a thing people have asked for. Similarly, we've always encouraged writing selector functions in the same file. And there's this weird dichotomy, where the slice and the reducer don't know where they are going to get added, but when you write the selector in the same file, oh, yeah, it has to be at state.to dos in order to get the value. This doesn't know where it lives, but the selectors have to know where it lives. Same file.
MARK: We've now got the ability to define selectors inside of createSlice and again makes a default assumption that it's going to be stored as slice.name. State.to dos or whatever by default. Again, there are limits. We don't know what the TS types of the rest of the state will be, so you can't really safely access state.posts or something from your to do slice. But filling in default scenarios that people probably want to do, and we have other fallback options for if it's getting added as state.other to dos or something, you can pass in a function that retrieves that value first, so that the rest of the lookups work okay.
MARK: This is something we're still not 100% sure on the exact final behavior on. We would really like people to try this out in the betas and let us know, hey, this works great, or I tried to do this other thing, and it doesn't let me do it, or I got an error, needs more options, that kind of thing.
MARK: And then we've -- Redux Toolkit has always depended on and re-exported createSelector for making memoized functions. We used that inside RTK query, and a few months ago I spent some time trying to fiddle with reselect and build out some new features. Reselect is very simple conceptually. It does a bunch of, like, reference equality checks, and that's simple and it's fast, but it also means there's cases where it has false positives and recalculates results when it didn't need to. The classic example of this is let's say I'm trying to select state.to dos and the output of a selector maps over that and adds improve.ID. So, we have an array of just the IDs. If I dispatch an action that toggles the completed flag that's going to make a new object that array index 3, make a new to dos array, because one of the values inside changed. And it's going to update, you know, root state. If you grab state.to dos as the input to a selector, that select to do ID's function is going to rerun, even though none of the IDs changed. And you're going to end up with a new arrays reference with the same ID values inside and maybe your list component re-renders when it really didn't need to.
JASON: Wait, what?
MARK: Yeah, I know, I know. But I was wondering if there's some kind of inspiration or mixing of the worlds that we could do. And I'd scrolled away some bookmarks of stuff that I'd seen over in the Ember world. One of the Ember folks had written an add-on several years back called tracked Redux, where they took a Redux store and wrapped it with some of the new functionality from Ember's recent glimmer rendering API that auto-detects nested field updates. Every time the Redux store would update, they'd shove the entire Redux state value into, like, a tree of proxy objects that tracked which fields in the state were being accessed. So if I accessed state.todos bracket 3.completed, it knows that chain of fields is being depended on by somebody. The next time you put in a new value, it's able to mark those as changed, specifically, and then intelligently determine, oh, just this one bit of the UI needs update. So, I spent a couple weeks digging through that code and some of the Ember libraries and whatnot, and some of those Ember folks had some really good articles on how this auto-tracking concept worked. And I was able to copy/paste and modify a lot of that stuff and build a new alternate memoizer for the reselect library that uses this to intelligently determine exactly what fields are being accessed and only recalculate if those specific fields change. And, so, it has different tradeoffs and performance characteristics. There was a number of cases where it would not update at all, and like your selector would recalculate less and your UI would re-render less often. There is more overhead in doing some of those checks. I don't yet know, like, how much of an impact that has in a real-world application. It's also got a funny little behaviors, like because it depends on checking nested field accesses, if you write a selector that just, like, immediately returns the input value, which, A, is bad, you should never do that, but sometimes people do, it will think nothing ever changed, because it never saw an access to a nested field in the results.
JASON: Oh, interesting.
MARK: So, it will run the first time and then never again.
MARK: So, I've made this available in reselect 5 alpha as an alternate option that you could switch to if you think it would benefit from the different performance characteristics. I also made a third option that's based on code I copy/pasted from the React library that uses weak maps to look at the arguments. And the idea there is that selectors have always defaulted to having a cache size of 1, which means if you try to share the same selector function across, like, many different components that pass in props.ID or something, they are all going to call it with different arguments, and it's always going to recalculate, because it's never seeing the same arguments twice in a row, so the cache size always gets blown. I had previously added an option a couple years ago, but you had to give it a fixed number ahead of time. Ten or 100 or something. Because this new approach uses weak maps, it should throw the values away as soon as nothing else depends on that object reference. And, so, in theory, I think, it kind of has like a cache size of infinity. And just intelligently gets rid of stuff when it's no longer needed. It's really hard to write tests for that. I was working with a guy at a hack-a-thon recently to try to write tests that verify these things go away when stuff gets garbage collected. So, these are pieces that feel useful. I don't have examples of them in real-world applications. But they seemed worth building, and I hope that they can help solve problems for people.
JASON: Cool, very cool. So, that leaves us with about nine minutes remaining. So, is there anything that you want to make sure we cover before I start tearing things down?
MARK: Let's hit the three big comparison points that I brought up as discussions. So, the number one question that we keep getting, and I probably should put this in our FAQs somewhere, what is the difference between Context and Redux and when should I use either. A year and a half, two years ago, I wrote what is intended to be like the single definitive answer to this entire topic, which is like a 10,000-word post on my blog. And the answer is, Context is not a state management tool. It is a dependency injection tool. It's kind of like passing one prop at a distance to any component that cares about it. You're actually, when you manage -- the management is actually done by you in whatever use state or use reducer or use memo or whatever. You're the one who is deciding what goes into the top of the context. That's the management part. Context is just a way to pass it down. Redux, on the other hand, is a state management library. It's meant to work outside of React, meant to work with React. The management is the store, the reducers, the actions. If you put use reducer and use Context together, they do start to resemble Redux and React Redux. There are a number of technical differences in both behavior and implementation. Limited to only working inside the React component tree. And there's currently no way to selectively re-render when part of the value in the context changes. You're only putting in one value, can be a primitive, an object, can be like a quiet fetching instance thing, but one value goes in, and every time you put a new reference into contextprovider.value, every component that reads from that will re-render, even if they only care about value.B and, you know, value.A got updated. Whereas React Redux lets you be more specific about which values you're extracting from the state. And it's got, you know, the dev tools and middleware and the ability to write all this logic outside of React. So, use context and use reducer are great tools. You should use them a lot. This is not an argument saying Redux is better or that you must use Redux. What I am saying is these are different tools with different tradeoffs and different use cases, and you should understand those differences, so you can make an intelligent decision about which tool best fits your current problem.
MARK: Not that I've repeated that 5,000 times before.
JASON: Did you want to -- I think you had one other one that you were going to talk about.
MARK: Couple others real fast. So, on the side effects thing. Like I said, we shipped the RTK listener middleware last year. And, so, our recommendation at this point is that you should use RTK query for data fetching, the default approach for data fetching in your application. It does all this stuff out of the box, it does all the caching and has the hooks for you. You really should not write data fetching code by hand either in a Redux application or a React application. So, use RTK query for the default. If you need to write Reactive logic, you know, this action got dispatched, kick something off, use the RTK listener middleware as the default approach for that. It does almost everything that you could do with Sagas and Observables, it's simpler, smaller, easier to use, better TypeScript support. And it's a lot easier to work with. There are probably a very few cases where, you know, Sagas have some very complex option that listeners don't, and you might have to fall back for that. But we really did design listeners to do almost everything that Sagas could. So, if you need to write Reactive logic, prefer this. Then the last question is, what about React query versus RTK query. This is not a competition. We love React query. We love the React query maintainers. Tanner is great. I finally got to meet Dominic at React Summit last week. In fact, Lens and I had dinner with Dominic, and people were taking pictures and saying, oh, they are conspiring about the future of client-side data fetching in React. We actually cross-endorse each other's libraries. If you're writing a plain React app, you should use React query. If you're using a Redux app, use RTK query. They are the same kind of tool. They solve the same kind of problem. There are differences in some of the APIs and capabilities. React query is more like let's define the fetching function at the place where we make the request. RTK query is more like let's define all the end points up front, so that we can use code generation and TypeScript and generate the hooks and all that kind of stuff. But they are fundamentally the same kind of tool, with roughly the same set of capabilities. So, use one or the other, depending on, like, what kind of app you're building. But this is not a competition in terms of like we dislike each other. They are both great tools. In fact, I'm hopeful that -- I finally got to meet Michelle, author of Mob X and Immer. Afterwards, we were talking about maybe at a conference in a few months we could do a talk or a panel about competing library maintainers and how we actually get along and love each other and take inspiration from each other. Maybe we'll get to do that at React Advanced or something in the fall.
JASON: With that, unfortunately, we're out of time. I got to end this here. Everybody, if you have more questions, you want to get direct into something with Mark, you can go find on Twitter, on GitHub, on all the places. Just remember, be kind. And then we also want to do a shout-out to the sponsors. We've had live captioning on this episode, like we do on every episode, and that's made possible through the support of Netlify, Nx, New Relic, and Pluralsight, who all kick in to make this show more accessible and allow us to do all the fun shenanigans that we do. Check out the schedule, so much good stuff going on. And thank you to Ashly from White Coat Captioning for doing the captioning today. With that, Mark, I think we are all set. Do you have any final words before I send us off to go raid?
MARK: Please, please use RTK if you're not using already. Please try out the betas and let us know how they work, give us feedback on what breaks or what we should try to tweet. Day job plug, check out Replay.io, if you haven't seen it yet, it will drastically simplify your debugging. And, please, don't yell at maintainers.
JASON: Drop a link to the time travel episode here. Then we're going to go and raid Martin Dowden. Everybody stay tuned for that, and we'll see you all next time. Thank you so much.
Closed captioning and more are made possible by our sponsors: