Let’s Learn Modern Redux!
A lot has changed in Redux since it was originally created. In this episode, Mark Erikson will teach us about Redux Toolkit an React-Redux hooks.
Links & Resources
Click to expand the full 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 LENGSTORF: Hello, everyone. And, welcome to another episode of Learn With Jason. We're bringing in Mark Erikson. How you doing?
MARK ERIKSON: Hey, doing pretty good.
JASON LENGSTORF: I'm really looking forward to it. And, so, for folks who aren't aware of who you are, could you give us a little bit of a background on yourself?
MARK ERIKSON: Sure. I am an answer of questions. I will answer questions about React and Redux anywhere there's a text box on the internet. I collect any link that looks potentially useful, or interesting, especially if it has to do with React or Redux. I write very, very long blog posts, mostly about React and Redux. Things about using Git, keeping a daily work journal. I'm a Redux maintainer. I've worked on various portions of the Redux libraries. Also, I'm generally-known as that guy with The Simpsons Avatar. (Laughter).
JASON LENGSTORF: It is a pretty notable avatar. I've seen that particular avatar for years.
Actually, I have a question for you, though. When you say, "very, very long blog posts," how long is a very long blog post?
MARK ERIKSON: My average full-size post is 5,000 words and up. I've written some that are longer. My posts, on a mostly-complete guide to a React guide on behavior, I've added to it and it's close to 8, 500.
JASON LENGSTORF: I get crap from my team because I'm incapable of writing anything close to 1,000 words. Thank you, Prismic. I saw you raid.
So, okay, you're a maintainer of Redux. Let's maybe start -- just for folks who have maybe only heard Redux by name or are unfamiliar with it -- what is Redux?
So, you can see what the state looked like, at that time. You can see the diff in the state, between previous and current. And, the list of the action event types kind of tell you what kinds of things occurred. So, the goal is to give you more confidence in understanding how the application is intended to behave and how it is behaving.
JASON LENGSTORF: Okay. Yeah. And, so, like, the thing -- the thing that I remember really made Redux stand out, back in the day, when it first kind of showed up on the scene, was this concept of time travel. And everything that you just described is the reason time travel is possible. You're not just making a change. You're creating these very well-defined steps that are, you know, you can cycle through them, forward and backward, which means you're not saying, "my app changed and people clicked a button and stuff is different." You're clearly defining, "somebody clicked a button, this action occurred." If I go back a step, then it's like unclicking the button. If I go back another step, it's like unnavigating a page. It's a really cool -- I had never seen that before.
And so, I mean, all of that sounds amazing and it sounds like it's the sort of thing that makes web development -- like, this is the perennial problem in React is we all say, "how do we manage state in React?" But for whatever reason, I've seen, like, there's been this kind of shift in sentiment where people have said, "well, but Redux is too much. It's too much boilerplate," right? First of all, I know we're going to talk about how that's being addressed. But I guess the flip side to it is how do you think that came about, in the first place? What do you think led to that shift?
MARK ERIKSON: Even from the beginning, like, some of the very early documentation, [Indiscernible] actually said, "Redux is not intended to be the shortest way to write code, it's meant to make your code predictable." I've tried to divide this into what I refer to as "inherent complexity and incidental complexity." So, some of this is stuff that is more work because that's the way Redux was designed. There's indirection. There's the process of having to define action objects and dispatch them and write your code separately.
If you have a hammer, everything you see is a nail. Once Redux got that initial adoption, everyone began writing tutorials saying, "if you're using React, you have to be using Redux." It was added to a lot of applications that really did not need Redux in the first place.
JASON LENGSTORF: That actually kind of -- that hits close to home for me, because where I started to feel like a resistance to Redux wasn't because of Redux. It was when everything that I was doing was, like, it was Redux, but then I also needed to add a Thunk and a saga. It was like -- it feels like we're taking a long walk to make this stuff fit when we could just, like, not do that stuff and use this other pattern that, you know -- Redux wasn't built for the thing we're trying to use it for right now. That was where I started to by, like, "is this the right choice?" And, it leads back to something that I think is kind of at the core of -- maybe -- I could say that it's experience, maybe it's just getting tired. Starting to ask the question "are we using the tool that's going to get us there fast?"
MARK ERIKSON: Yes. That's really the biggest point I've been trying to make, in these discussions, over the last few years. I don't want everyone to use Redux. Redux is not the best tool for every situation. What I want is for people to take time to understand the trade-offs, the different available tools make, look at the actual problems that you have to solve in your own application and try to choose the tool that best-solves your specific problem.
Now, related to that, like, most people -- number one, most people don't take the time to think about any of this at all. But a lot of people don't know what the trade-offs are, leading to these different tools, as a quick side note. A couple months ago, I actually tried to get this idea off-the-ground where I proposed a site where I'm calling the "React Community Tools and Practice." Kind of inspired by the React TypeScript cheat sheet, where the community sits down and tries to assemble a list of common tools and practices that get used and provides, like, best-faith descriptions of "here's the problems each tools solve. Here's when you might want to use it." Ideally written by an expert on each tool. I threw up a repo. There was a little bit of interest. I created a prototype of the site and added, like, two pages on what is state management and what is Redux, but I've been busy with a lot of other stuff and haven't had time to push it further.
JASON LENGSTORF: David is talking about making a PR, which would be about X State. You said something, earlier, that I want to call out because it actually reminded me of a conversation I had with David about the idea of inherit -- what did you say? Inherent complexity?
MARK ERIKSON: Stuff that is built into the thing that cannot be used. This is stuff that kind of comes along with it, but isn't necessarily.
JASON LENGSTORF: Yeah. And that reminds me of a slightly-different take on that, that David had. Why does X State have so much code? You're defining the complexity that was already there. I think that that is also a really interesting way to look at this. What is the boilerplate for? The boilerplate is so we're not guessing what's going to happen later. We're writing this code so that we can make things predictable.
MARK ERIKSON: Yes. So, that comment in chat, right there, is a good example of it. (Laughter). Every Redux tutorial you've ever seen, up until the last year or so, uses a switch statement? Why do we use switch statements? They are interested in the current value of the "action.type" field. Looking at multiple values of one field, it's a switch statement. Do I have to do it? No, I could use an "if else." But it's not required.
JASON LENGSTORF: Uh-huh. Yeah. I think that's -- yes. Like, getting -- getting the disambiguation between what is actually necessary and what is dogmatic about every tool is always -- that's tricky because, you know -- I also feel like there's this bell curve that I've seen about, like, when you're learning, you don't have a lot of opinions and when you have a little bit of knowledge, you have a lot of opinions and then as you get more experience, you have far less opinions. [No audio] articles about tools and they're like, "you must do it this way or you're not doing it right and I'll be angry and yell at you in the comments!" There's a certain sense of, like, by the time we've gotten the experience or the wisdom to know that it doesn't really matter. You can build this however you want, just make sure you understand the trade-offs.
We're all so damn tired. (Laughter). I like the way you're coming at this. I love the idea of this site, to compare, let's have the maintainers of these tools talk about this is what it's built for. This is when you should consider not doing it. I love that.
Okay. So, that is kind of the context, that's the history of what got us to today. We're moving forward, right. We've been working on some really exciting stuff that lets us keep all these benefits, but reduces the boilerplate?
MARK ERIKSON: Yes, absolutely. There's a couple different pieces here. So, one -- some people have probably seen by now, in 2018, React releases hooks to have state and write custom logic. About 30 seconds after that announcement, people were filing issues asking, like, "Redux hooks, when?" (Laughter). We had to go through some rearchitecting of React-Redux and we had a multi-month, issue threads trained to design the API. But we launched our React-Redux Hooks API. The old API is still there, it still works. It's not going to break. We're not going to remove that. At this point, we recommend using the Hooks API as the default because it's -- well, for one thing, it aligns with where React is going. It's a lot simpler, conceptually and code-wise. Connect confused a lot of people. Like, while "use selector" do the exact same thing, the way they look in the code just clicks with people more.
JASON LENGSTORF: I do remember, when I was at IBM, we used the Connect pattern and that was always a hurdle for people, was trying to get their head around "how do higher-order components actually work?" Right, because it feels like magic. I wrote this function -- I wrote this component and if gets magic props. So, you had to do this whole, like, whiteboarding exercise to explain how higher-order components. With hooks, I call a function and I get a dispatch.
MARK ERIKSON: Another nice benefit is that TypeScript has taken over and we recommend that people use TypeScript and Connect is really, really hard to use with TypeScript and "use selector" and "use dispatch" are trivial. You say, "here's the argument. Here's the result. Done."
JASON LENGSTORF: One thing that I like about TypeScript is it has led to significantly-less code because people have to write it.
JASON LENGSTORF: Yeah. I feel we could talk about this in the abstract for hours. But I would love to actually get my hands dirty this. I've never tried Redux Toolkit. So, first, before we get started, remember, this show is being live-captioned. We've got White Coat Captioning here with us today, thank you so much for being here. We've got Netlify, Fauna, Auth0 to make this show more accessible to more people. Make sure you head to the home page if you want to see those captions and remember that these are clickable if you want to go check out the sponsors.
We are going to be talking about Redux Toolkit today and specifically, I think we are -- here's -- this is more of a migration guide?
MARK ERIKSON: The older way to write Redux code.
JASON LENGSTORF: Perfect. I will leave the rest of these links for when we get to them. So, for now, if I want to get started, what's the first thing I should do, as a raring to go, Redux hopeful?
MARK ERIKSON: We actually have a pair of pre-defined templates for Create React App that come with Redux Toolkit and the React-Redux API already configured, so if you Create React App, My App -- for most people, Create React App, or "--Reduxtemplate." If we skip the pre-defined setup and actually add everything ourselves --
JASON LENGSTORF: Yes.
MARK ERIKSON: How would this look like? Just for an added degree of difficulty, we're going to use something other than Create React App. We are going to use Vite. It uses different pieces inside. It also has a command you can use to spin-up a brand-new project right away.
And so while we don't have a Redux template for Vite, there is a React and TypeScript template for Vite.
JASON LENGSTORF: Okay. For me to do that, I'm going to use -- let's see...so, I'll do "NPM VJS" and then I want TypeScript?
MARK ERIKSON: Yes.
JASON LENGSTORF: So, I'm going to call this...what did we call this episode? We should probably call it something that will show up in Google, right? Redux Toolkit and then we want to use the template and the template that we want to use is "React TS"?
MARK ERIKSON: If you don't supply the template, it gives you the prompt and asks you to select from the menu.
JASON LENGSTORF: Wow! Look at that! Okay. So, let's do the React.
MARK ERIKSON: So, pick React and then it'll ask you which one you want. So, TypeScript it is.
JASON LENGSTORF: Excellent! That's super-cool. Wait -- what?
MARK ERIKSON: That was -- that was fast.
JASON LENGSTORF: I thought it broke. (Laughter). I am so blown away by just how -- oh, my God -- how fast this stuff is. So, let's -- actually, let me "get init" this and then we'll open it up. So, I have a very bare-bones-looking site here. Here's our index.html. It's got a root we're going to map our React into. What else is going on in here?
MARK ERIKSON: Very, very similar to your typical Create React App template. The only difference I saw was the entry file is called "main.tsx." Structure and content is otherwise basically identical.
JASON LENGSTORF: Oh, I need to run my "NPM install."
MARK ERIKSON: Yep. And the nice thing here is that Vite has many different dependencies than Webpack and Create React App does.
JASON LENGSTORF: This is also something that I really enjoy is when, look, oh, my goodness, one scroll -- one flick of the scroll wheel gets me all the way through node modules. This is not the heaviest object in the universe, it turns out.
Okay. So, yeah, looking at this production dependencies, dev dependencies, the types for React and React DOM. TypeScript, itself, for compiling. And then Vite for building.
MARK ERIKSON: Let's just start this in development, just to see what we're starting with, and then we'll make some changes. "NPM run dev," it starts up instantly.
JASON LENGSTORF: I'm still blown away by how fast it is. Here's our simple, default Vite project.
MARK ERIKSON: So, let's go ahead and kill the dev server and now let's -- then we're going to add some React packages to this.
JASON LENGSTORF: Okay.
MARK ERIKSON: So, let's [audio cutting out] NPM@ReduxJS@Next. We're going to install a beta version here.
JASON LENGSTORF: "ReduxJS," no hyphen?
MARK ERIKSON: Correct. Let's add Axios as an HTTP library, too.
JASON LENGSTORF: There we go.
MARK ERIKSON: Cool. Okay. So --
JASON LENGSTORF: The chat is talking about Edge. They called it "Chrome in a hat." You're not wrong. I'm loving Edge. Have you been using it at all?
MARK ERIKSON: I'm mostly a Firefox person.
JASON LENGSTORF: I have an older MacBook and it wasn't working.
MARK ERIKSON: All right. So, let's start by writing a bit of Redux logic and just like about every tutorial ever, let's start by making a small counter example. So, let's actually start by creating a new folder, under "source." And let's call it "features." And this is something that is probably going to be different than what people have seen in Redux apps before. Because typically you see a set of folders named "actions, reducers, constants," all that kind of stuff. And that's never been required. Redux doesn't care about your file structure, but that was always the pattern we showed in our docs, was a folder-by-type approach. What we recommend today is a feature folder idea. You pick some concept, in your application, and we're going to put all the code related to that feature in one folder, or occasionally even in one file. So, let's create a folder named "counter" inside of here.
JASON LENGSTORF: Okay.
MARK ERIKSON: And inside of there, let's create a file called "counterslice.ts."
JASON LENGSTORF: Like that?
MARK ERIKSON: Hyphenated, doesn't matter.
Historically, you would have seen your Redux logic for one thing, split across four separate files for your action type, your reducer. What we recommend today is putting as much of the logic for a given feature as possible into a single file. And, we typically refer to this as a "slice file," because it represents the logic and the data for one slice of your Redux state. You might have also heard this as the Dux pattern.
JASON LENGSTORF: Dux?
MARK ERIKSON: Like, "quack, quack." We're going to import a function and a type. We're going to import a function called "create slice," and a type called "Payload Action." From "@Redux.com/Toolkit. "Create slice" is the main API function you're going to use to define your Redux logic and Payload Action is a TypeScript type that represents, this is the contents of one given object. We're doing a counter here and really, all we need for our state, is a number. We're going to put that number inside an object. So, let's define an interface called "counter state."
JASON LENGSTORF: Okay. "Interface counter state."
MARK ERIKSON: And let's just give it a single field called "value," that is a number.
JASON LENGSTORF: Is that a number?
MARK ERIKSON: "Value:number." This is going to represent the shape of the state, inside of our slice that is managed by the reducer. Now, we also need to define the initial state for this slice, so let's do "constinitialstate." Let's add a ":Counterstate=newobjectvalue:0." We have the type of our state, the initial value and now we're going to define the slice. So, "constcounterslice=createslice."
JASON LENGSTORF: Look at the auto-complete.
MARK ERIKSON: Yes, it's complete. "Create slice" takes one parameter, which is an object. And the first options field that we need to pass in is a field called "name." And this is a string and the slice is going to use this, in a couple ways we're going to talk about in a minute. Let's called this "counter."
JASON LENGSTORF: Does it need to be uppercase, lowercase?
MARK ERIKSON: I use lowercase.
The next is the state. We can use object shorthand to say "initialstate." The third is called "reducers." This is where we're going to define the different types of logic and updates that we're going to have inside this reducer. So, what are the typical things that you can do with a counter? You can make it go up. You can make it go down. You can add some other amount to it. Let's start with the obvious, simple one. We're going to want to have a case where every time an action is dispatched, we just add "1" to the counter. Let's make a function in here called "incremented."
JASON LENGSTORF: Do you have a preference?
MARK ERIKSON: Let's write it as an in-line object function. So, the word "incremented(state -- actually, we don't need an action, in this case. So, let's just backspace the comma there. ES6, literal syntax. You can define. So, in a normal Redux Reducer, what we would do is "return...state," to make a copy of it, for an immutable update and overwrite the value field with an old value, plus one. Redux Toolkit lets us simplify that process. So, rather than doing that, we can write what looks like mutating code, directly in this reducer. We don't even need a return keyword here. So, all we actually have to write here is just "state.value++."
JASON LENGSTORF: Oh, I forgot about that.
MARK ERIKSON: So, in a normal Redux Reducer, this would be really bad because we're mutating our existing state, we're not making copies, the UI won't know we've made any changes. With Redux Toolkit, it uses a library called Immer. It wraps our state updates and tracks all the mutations that we try to do. And, once we're done, it actually kind of replays them and turns it into a safe and correct, immutable update, as if we'd done all of that copying and spreading and mapping and everything ourselves.
So, this drastically-shortens the amount of code we have to write. Let's hook up the rest of this and we'll come back and add more logic later. Let's scroll down below the counter slice. So, typically with Redux, you see "action creators." It's a function that makes -- and returns -- an action object. And typically, you'd write those by hand. Create Slice actually just made one of those for us.
JASON LENGSTORF: I'm just adding this node so that I don't forget, later, when I'm trying to explain it to somebody.
MARK ERIKSON: Documentation is a wonderful thing. So, this Slice Object has a couple different fields in it. One is a reducer function [audio cutting out] and it also has generated an action creator for each of the different functions inside the reducer's field. So, let's do "exportconstcurlybracesincremented=counterslice.actions."
JASON LENGSTORF: Okay.
MARK ERIKSON: And if we mouse over "incremented," we should see a TypeScript-y pop up. An action creator without a Payload. Our incremented reducer says we don't need an action object so we really don't care about it. All we need to know is that something happened.
JASON LENGSTORF: And it's smart enough to know if it doesn't have an action, to just change the type.
MARK ERIKSON: Yep. So, we'll look at another case after this. We'll export "defaultcounterslice.reducer." So that is the basic shape of what we call a Redux Slice. You use the Create Slice API, define your state, one or more reducers, export action creators, export the reducer that handles all those cases.
JASON LENGSTORF: This is slick. And we're not using this yet, right?
MARK ERIKSON: So now we need to set up a Redux store, which is a one-time thing, for application. So, the typical folder structure I use, here, is let's make a new folder, under "source" called "app."
JASON LENGSTORF: Okay.
MARK ERIKSON: Inside of there, let's make a folder called "store.ts."
JASON LENGSTORF: A file?
MARK ERIKSON: Yes. We'll open that up and we're going to import a function called "configurestorefromReduxToolkit."
JASON LENGSTORF: Okay.
MARK ERIKSON: So, this is a wrapper around the basic Redux Create Store Function. It automatically sets up a store with the right defaults. For example, it automatically turns on the Redux Dev Tools Extensions. It automatically adds the Thunk middleware and automatically turns on a couple of development checks that catch common mistakes, like accidental mutations.
So, now we need to import our counter reducer --
JASON LENGSTORF: Oh, wait, that's counter reducer, it's the default.
MARK ERIKSON: Yeah, so no curly braces. From "..features."
Okay. So, let's make our Redux Store. So, "exportconststore=configurestore." And one thing you'll notice is this also takes an options object as its parameter. So, empty object. And we're going to add a field called "reducer." And you could pass in an entire reducer function here, but you remember how Redux has that Combine Reducers Function to mash a bunch of reducers together? The Store will do that automatically if we pass an object. So, let's pass an object here. And a field called "counter."
Let's call it a "field counter" and give it a value of "counter reducer." This will call Combine Reducers so we end up with a "state.counterfield" in our state.
While we're here, we're going to do some very specific TypeScript things. We're going to export some types that are based on our store itself. So, export type and we're going to call this "app dispatch." And this equals the type of store.dispatch.
JASON LENGSTORF: Store.dispatch.
MARK ERIKSON: So we're taking the store's dispatch function and we're asking TypeScript, "what is this thing?" We're exporting the type of that function as a thing we can use. Also, export a type called "root state." And that equals -- and this is going to be a little bit magical -- TypeScript has a built-in type called "Return Type. Angle brackets and then, "typeofstore.getstate."
JASON LENGSTORF: Like that?
MARK ERIKSON: Let's mouse over "Root State." It has a field called "counter" that is the counter state?
JASON LENGSTORF: Okay. All right.
MARK ERIKSON: There's nothing specific to Redux Toolkit about this. It's using TypeScript's inference to figure out as much as possible, so that we don't have to declare this ourselves. And so if we add more slice reducers to our store, that type updates automatically.
JASON LENGSTORF: Gotcha. That's slick. This is really nice.
MARK ERIKSON: All right. So, we've got two more setup steps we need to do. Let's go to "main.tsx."
JASON LENGSTORF: You said "main.tsx," and now we're going to get our -- is it curly braces provider?
MARK ERIKSON: From React-Redux. And then we're going to want to import that store variable we just exported. So, import, curly braces, store. From .app./.
JASON LENGSTORF: Provider=store. Auto-format for me? Fine. Okay.
MARK ERIKSON: We don't have Prettier installed? How can we write this application without Prettier installed?
(Laughter). Here's our typical one-time setup. Tell you what, let's go back to the browser. Do you have the Redux Dev Tools Extension installed?
JASON LENGSTORF: Not on this browser, but I can, very quickly.
MARK ERIKSON: Yeah. Let's add this really fast. This is, like, one of the first things I do on a new computer. Are we on Edge or Chrome here? There we go.
JASON LENGSTORF: Okay. Done!
MARK ERIKSON: Okay. So, let's restart our ViteDevServer.Ts.
JASON LENGSTORF: Unbelievable. Okay.
MARK ERIKSON: Refresh the page. Right-click anywhere, "inspect element" or that works, too. Actually, that button, there, works fine. If we look at the Dev Tools Extension, in here...we should see -- click on "init" on the left. And click on "state" on the right. Look, there's our counter state.
JASON LENGSTORF: Beautiful.
MARK ERIKSON: Okay. So, we've got the basic setup working, but there's one other thing we need to do before we can start using this in our components. So, let's go back to that app folder and make a folder called "hooks.ts." Is a new pattern we've recently started recommended for people using TypeScript. React-Redux has hooks and there are TypeScripts that say how those hooks work, but those hooks don't know anything about the specific state. So, what we've found is it's best to create pre-defined versions of those React-Redux hooks that already know the right types for our state and our dispatch.
So, we're going to start by importing three things from React-Redux. So, start by doing -- let's [audio cutting out] and then we'll fill these in because we'll get the import a little easier. The first one is "Type Use Selector Hook."
JASON LENGSTORF: Okay.
MARK ERIKSON: Second is the "Use Dispatch Hook." And the third is "Use Selector."
JASON LENGSTORF: That is much easier. I should remember to add the import name all the time.
MARK ERIKSON: Python, the file names come first and then the imports and I really wish ES6 had done that. We're going to define pre-typed versions of "use dispatch" and" use selector." Exportconstuseappdispatch=arrowfunctionimplicitreturn." Skip the curlies. And, we're going to do "Use Dispatch Angle Brackets," for a generic type. And now -- whoops, we actually need to import those two types from our store file. So, let's go back up a line. My bad. I forgot -- I forgot a step. So, import from ./store and let's import the root state and app dispatch types.
JASON LENGSTORF: So, I'm guessing "use dispatch" is going to be "add dispatch?"
MARK ERIKSON: Correct. "Exportconstuseappselector." Before the equal sign, we want a colon. And the type, here, is "typeduseselectorhookanglebracketsrootstate." And then, this "=useselector," but with no parenthesis.
JASON LENGSTORF: Got it. So, this is -- let me repeat this back, because this is a function. We're aliasing it, but adding types.
MARK ERIKSON: Exactly.
JASON LENGSTORF: Okay. I understand.
MARK ERIKSON: Now we're exporting two hook variables that have the right TypeScript types to find and in our components, we're going to import these hooks from the hooks file, instead of importing the base functions from React-Redux.
JASON LENGSTORF: Gotcha.
MARK ERIKSON: Okay. So, let's go add this to the UI real fast. Let's open up our app.tsx file. For simplicity, let's skip it. Hey, look, this template already has a counter example in here. I actually forgot about this. (Laughter). This is great, actually. This is perfect. So, right now, the counter is driven by React State. So, let's import those two hooks from our hooks file, up top. So, import from./app/hooks.
JASON LENGSTORF: And then we want "use" and "use selector."
MARK ERIKSON: And then we're also going to want to import the incremented action creator for our counter slice file.
JASON LENGSTORF: Okay. So, features, counter slice. And we want "incremented action."
MARK ERIKSON: Okay. So, let's delete the line with the "use state."
JASON LENGSTORF: Okay. Get rid of the "use state."
MARK ERIKSON: The first thing we want to do is grab the current value of the counter so we can display it. We're going to do "constvalue=useappselector." And this takes a selector function and we could write it separately or we could write it in-line. So, it takes one argument, state. So, "statearrowfunction."
JASON LENGSTORF: And we just want "state value"?
MARK ERIKSON: State.counter.value. Now you see the usefulness of having this pre-typed. We didn't have to say that state was a root state, that's already built in and we get the nice auto-complete.
JASON LENGSTORF: I should actually show that. We can see here, I didn't have to actually guess -- which I did. So, instead, I could have just been patient, hit the dot and seen "counter" was there and hit dot again and seen the value was there. So, it saves me from myself. (Laughter).
MARK ERIKSON: If we mouse over "value," we should see, on the left, that it's a number.
JASON LENGSTORF: Uh-huh.
MARK ERIKSON: Over on the left side, the extracted one.
JASON LENGSTORF: Got it.
MARK ERIKSON: So, we should be able to -- even to be consistent, let's actually call that "count," because we're already inserting a variable called "count" in our output.
JASON LENGSTORF: Now we've got our types down here.
MARK ERIKSON: Let's temporarily remove the "onclick handler."
Okay. Let's hit "save." And the page should auto -- actually auto-reload. Just for kicks, let's go back to our counter slice file...let's just change the initial value to a 5 or a 10. That's proof that the data is coming from the Redux Store, to the component.
So, it's a button. What do we do with buttons? We incremental things when we think on them. Now we want to dispatch that incremented action. With the old Connect API, we would have kind of abstracted this by using the Map Dispatch and getting creators as props. We've removed that abstraction. You write a little more code, but it's more obvious what's happening.
So we're going to get dispatch, itself. "Constdispatch=appdispatch." Call the hook. Mouse over it. Oh, it's a fancy, complicated --
JASON LENGSTORF: But it's got a counter in there. Look at it go!
MARK ERIKSON: Now we have access to dispatch. We need to make our click handler. So, let's go back to the button, add the "onclick."
JASON LENGSTORF: I'm going to -- I'm going to put it up here.
MARK ERIKSON: We can totally separate it out. Whatever your preferred syntax is.
JASON LENGSTORF: Down here, then, this is just "handle click." And we don't really need to worry about the event because we've not preventing anything.
MARK ERIKSON: All we're going to do, in here, is "dispatch(" and dispatch what is returned by that incremented creator." So, let's hit "save." And let's start clicking buttons.
JASON LENGSTORF: Look, look, look, look! Here's our time travel. Now, I'm going to jump -- oh, my goodness. We're time-traveling, everyone.
MARK ERIKSON: We have the word "counter" in the action types and also the word "incremented." Where did those come from? If you look at that counter slice file, this is where the name of the slice comes into play. It automatically generated those action type strings you used to write by hand, by just combining the slice name field with the literal name of the case reducer function that we wrote.
So, we just give your functions obvious names and you get the action types and stuff, for free, you don't have to do anything special.
JASON LENGSTORF: This is slick. This is really, really nice.
MARK ERIKSON: Let's do one more, quick thing here. Let's say we want to, like, add a specific number to our existing value? So, let's add a second case, here, in the slice, called, like -- like, "amount added" or something. So, just below "incremented."
JASON LENGSTORF: Just below "incremented." "Amount added." This is the Payload Action?
MARK ERIKSON: "Payloadactionanglebracketsnumber." So, that's saying when we get this action object, the payload field is going to be a number. And, just like anything else, it could be an object, an array, a string. For this case, it's a number. Well, what are we going to want to do? We're going to want to add that to the existing [audio cutting out]+=action.payload.
JASON LENGSTORF: Okay. All right. So, then for me to -- for me to do this, then, I would just --
MARK ERIKSON: You need to export the action creator that just got generated.
JASON LENGSTORF: Got it. Okay. So, let's export that "amount added." And then in my hooks, I don't need to do anything. In my store, I don't need to do anything. So I just go back here.
MARK ERIKSON: "Import amount added." And, let's make a second handler -- or, we could do this -- yeah. Just add some known amount to it. That works.
JASON LENGSTORF: So, we'll increment by three here, so it should go up be 3s.
MARK ERIKSON: So, that's the basics of writing slices. You define an initial state and a type, you define the case reducers. For every action object that one of those reducers takes, you use the payload action type to say, "here's what's inside of the action.payload field."
JASON LENGSTORF: Excellent.
MARK ERIKSON: So, that's the basics of what we call "modern Redux." It's React-Redux Hooks. It's Redux Toolkit for the store setup and the logic. It's the newer patterns that we recommend, like writing these slice files to put the logic in one place, using TypeScript, exporting the type hooks. All that, together, it's the same Redux that we've always had, but the code you write is so much different than it would have been just a couple years ago.
JASON LENGSTORF: Yeah. I mean, you can see, like -- so, we're increment by one if we use this one. Increment by...fixed amount. Having written a lot of Redux code, I -- I can see how much of a difference this will make in -- in, like, modern applications.
So, this is a huge leap forward. Excellent work here. This is really great.
MARK ERIKSON: So, in the interest of trying to get to the thing that I really want to show --
JASON LENGSTORF: Yes.
MARK ERIKSON: Before we wrap this up, let's take a look at a couple docs. Let's skip the middle section that I was hoping to have us do and then we'll skip ahead to the final section. So if we go to the Redux Fundamentals Part 8 and look at the Writing Thunks section, there, kind of in the middle. So, we've always encouraged the use of a code pattern called Thunks For Writing Our Asynchronous Logic. Thunk is a middleware that allows a store to accept a function as a dispatched thing. It's basically a way to let you write logic that talks to a store, ahead of time, separate from your components, without knowing what store it's going to talk to.
And so, it gives you access to dispatch and "get state." And then it kind of says, "do something with it. Have fun." The standard pattern that we've taught is you would dispatch an action before you make an Ajax request, make a request, then dispatch a success or failure action, based on how the result resolves. And the idea is, you would track, like, a loading state field so that you can show, like, your spinner or an error message or something like that.
So, Redux Toolkit has a -- an API called Create Async Thunk, that is meant to handle that specific handler afterwards. So, real fast, let's just look at a code example here. So, if you scroll up slightly, at the type of this code block, it shows what Create Async Thunk looks like. The first one is a partial action string. This is the one place where you'd still write those. It's going to generate three new action types based on that. One for pending, one for fulfilled and one for rejected.
The second argument is a function that is going to make some kind of an async request. In this case, let's say we have a little abstraction layer around, like, fetching some to-dos from a server, so we're going to make a request, await it and then return a field that was in the response.
Create Async Thunk will automatically-define three different action creators and action types and it will automatically dispatch -- pending -- before this function runs. It will run the function and either dispatch the fulfilled or rejected actions, depending on how the result resolved.
In our slices, because we've defined this Thunk outside of Create Slice itself, we need a way to respond to these other action types that aren't part of the slice.
JASON LENGSTORF: Right.
MARK ERIKSON: So, we can use this extra reducers field and it's got this Builder Callback API inside. I can say, "hey, Builder, I want to listen for the action type associated with the Thunk.pendingActionCreator." I can say, "here's our action, when it's dispatched and I want to update to loading." When the result comes back and it dispatches the fulfilled action, then I want to take the data that I got back from the server, maybe reformat it a little bit. Update the state to contain that data and I want to reset my status flag.
So, Create Async Thunk simplifies some of the patterns for working with Ajax requests and dispatching actions and Thunks. Exact same pattern, exact same behavior, just a little less code. Honestly, even with Create Async Thunk, you have to write the Thunks and reducers and manage the loading state and it's still kind of a pain. And what we've seen, over the last few years, is that the React ecosystem, in particular, has shifted from this idea of state management, as the problem space, to focusing on server data fetching and caching. And this is where, like, libraries, like React Query and Apollo have become popular and they've converged on similar APIs and patterns. Users don't want to write Thunks and reducers. They just want to say, like, "here's my URL. Magically get it for me and give me back, like, an "is loading" field." And so, you can use Redux for doing that, but you have to write all the code yourself, until now. So, my co-maintainers, [Indiscernible] and Matt have been building a data-fetching abstraction on top of Redux Toolkit and it takes very obvious inspiration from libraries like React Query and Apollo and SWR. When we look at the RTK Docs page, over there, this is our beta preview. Redux Toolkit, in its upcoming release, will have an additional API section that provides this data-fetching abstraction built specifically for Redux.
And, it's going to have some very unique properties, like, since Redux and Redux Toolkit don't care about your UI, you can use RTK Query with any UI layer. React, Vanilla JS. But we also actually have some React-specific behavior, where we will auto-generate React Hooks for the API endpoints that you define.
JASON LENGSTORF: Nooooo, way! Okay.
MARK ERIKSON: So what we're going to try to quickly do, we're going to try to set up RTK Query in our application and fetch a list of some dogs.
JASON LENGSTORF: Okay. So, we've got dogs-as-a-service ready to go here.
MARK ERIKSON: I've created an API code that we'll need to add to our codebase here. Let's go back to our code.
JASON LENGSTORF: What I'm going to do is I'm going to try to get this API key -- how do we make it available to Vite?
MARK ERIKSON: We're going to paste it into the code somewhere.
JASON LENGSTORF: [Audio cutting out].
MARK ERIKSON: I created it off of the free tier. Inside our "features" folder, let's create a new folder called "dogs."
JASON LENGSTORF: And "dog slices"?
MARK ERIKSON: Let's call it "dogs API slice." Again, it's just naming. It's just files. Call it whatever you want.
Let's go ahead and paste that API key in this file, just so we've got it available.
JASON LENGSTORF: Okay.
MARK ERIKSON: Okay. And, let's go up a couple lines and we're going to import a couple things from this beta version of Redux Toolkit. So, the -- we actually have this available as a nested import, so it's Redux/js/Toolkit/query/React. So, this is an optional add-on. You don't pay for it unless you import it. We're going to import Create API anymore another method called Fetch Base Query.
JASON LENGSTORF: Got it.
MARK ERIKSON: Okay. So, now we need to define the expected data type that we think we're going to be fetching back from this API. So, let's make an interface called "breed." Because the API endpoint we're going to be hitting is different types of dogs.
JASON LENGSTORF: Right.
MARK ERIKSON: First two fields are ID String and Name String. And then it's got a nested object called "image," which has a field called "URL," that is a string.
JASON LENGSTORF: Okay.
MARK ERIKSON: Okay. So, let me double-check what we got here. So, we -- we are going to define what we refer to as an API Slice. And the idea is, you're probably only going to have one or two of these in your entire application, like, let's say you've got, like, a typical server that has blog post example. Endpoints for users, posts and comments. You would not define separate API slices for each of those. Instead, you would define one API slice for the base server and you are going to define different endpoints inside of that for each of the different subURLs that we would hit.
So, let's create "constdogsAPIslice=createAPI." And the first parameter this needs is a string field called "reducer path." This is going to help the code know, when we attach this to our Redux Store, where are we keeping the data in the reducers? So, let's just call this -- as a string of -- "API."
JASON LENGSTORF: Okay.
MARK ERIKSON: The next argument is, we need to tell this, how are we going to fetch our data? There's many libraries. There's the Fetch API built into browsers. There's API like Axios and other stuff. You know, people write their own wrappers around Fetch. RTK Query comes with its own wrapper called "Fetch Based Query." So we're going to define a fetch based query and pass in an object. The first parameter is the URL that we're going to hit, as the basis for this API. So, "base URL." It's "https://API.thedogAPI.com/v1." And because this wants some authentication, we're going to need to add our API key. Let's add a "prepare headers" that is a field function.
The parameter, here, is a value called "headers."
JASON LENGSTORF: Headers.
MARK ERIKSON: And, we are required to return the headers object from the function.
JASON LENGSTORF: Okay.
MARK ERIKSON: And, before that, what we're going to do -- just return headers.
JASON LENGSTORF: Oh, gotcha. Return headers.
MARK ERIKSON: And then before that, we're going to set a header. So, "headers.set."
JASON LENGSTORF: Name -- and so this one is?
MARK ERIKSON: X.API.
So, that sets up what server we're talking to and passes through the auth header. So, if you've looked at React Query or SWR, React Query expects you're going to pass in a fetching function when you use the hook in your component. And that's typically where you would specify the URL. RTK Query takes an approach where we want you to try to define the expected endpoints up front, as part of the structure. So, we're going to add a field called "endpoints." This is also a function that takes a Builder parameter.
JASON LENGSTORF: Builder?
MARK ERIKSON: Yep. And, we're going to return an object, inside of here. And the fields, in this object, are going to be "endpoint definitions" generated by that Builder. Can RTK work with Angular? Yes. In fact, there's someone who's been putting together an RTK Query plus NgRx.
We're going to define a field called "fetch breeds." Doesn't even need quotes. And the value, here, is we're going to call "builder.query." And, we need to pass in some Angle brackets generics here. And, what are the arguments that we're going to pass in to the parameter generation function we'll add in a minute? So the return type is going to be an array of breeds.
JASON LENGSTORF: An array of breeds. Do I just do it like that?
MARK ERIKSON: Breeds, square bracket. Comma. It is going to take an optional number as its parameter. "Numberpipevoid."
JASON LENGSTORF: "Numberpipevoid."
MARK ERIKSON: Closed angle brackets, open parenthesis, object...and, we're going to pass in one field in here. So, what we're going to do is we're going to set up some query -- we need to say which of the nested endpoints, at the server, are we going to hit? And we're going to need to define a query parameter so that we limit the number of responses that come back. So, we're going to add a field called "query."
JASON LENGSTORF: Got it.
MARK ERIKSON: And this is going to accept that optional number as its parameters. This is a function.
JASON LENGSTORF: Sorry.
MARK ERIKSON: Let's call the parameter "limit."
JASON LENGSTORF: Okay.
MARK ERIKSON: Let's make it default to 10. So, limit equals 10 as a default argument. And we're going to return a template string and inside of here we're going to return "/breeds," question mark, for the parameter. Limit=$curlybrackets. It is based on the argument, right here.
JASON LENGSTORF: This is smart enough to know that it -- like, the base query sets us up, here, and so every query that we make is going to be the dogAPI.com and --
MARK ERIKSON: Okay. So, this -- the part we've done, so far, is all common, whether or not we're using React and if we weren't using React, it actually [audio cutting out] and some Thunks and some logic that we're going to need as part of our setup. So, we need to do a couple more things here. Let's export -- actually, let's just slap an export on line 13, in front of the API slice.
JASON LENGSTORF: Got it.
MARK ERIKSON: And let's go back to our Store file.
JASON LENGSTORF: Here's our store. And it's going to be dogAPI...what did we --
MARK ERIKSON: DogAPI...featuresdogs -- yeah, we named-exported it.
JASON LENGSTORF: API Slice, from Features, Dogs, Dogs API Slice.
MARK ERIKSON: So we need to do two things here. We need to add the reducer to our store. So, let's add -- we actually conveniently have the field that we described earlier. So, let's go down a line and do square brackets. And, let's do "APIslice.reducerpath."
JASON LENGSTORF: Oh, I got it. Okay.
MARK ERIKSON: Um...and...I'm trying to double-check something on my side and I'm actually getting TypeScript errors. I'm afraid I might break something. The other thing we need to do, to really make this work entirely correctly, is we need to add the custom-generated middleware that is part of the slice to the store.
JASON LENGSTORF: Okay.
MARK ERIKSON: So -- there we go. So, let's go down a line. So, RTK's configure store simplifies the process of adding middleware to our setup so we're going to go with the middleware field and this is going to be a callback function and the name of the parameter it takes is called "get default middleware."
JASON LENGSTORF: Okay. Did it just import that?
MARK ERIKSON: It did. It did. But we actually want to remove that. It's an export that, one of these days, we need to get rid of because we changed how it's used.
We want to return. "Callgetdefaultmiddleware," by itself. That's something that normally happens internally, but now we're going to expand that. The combined middleware is everything, by default, plus this. So now we're going to say [Indiscernible] API.slicemiddleware.
JASON LENGSTORF: The chat is very excited about dogs.
MARK ERIKSON: This will add special capabilities to track, like, cache lifetimes. If no part of the codebase needs this, we can remove the cache after it expires. We've got 10 minutes left. We can do this. We can do this.
Let's go back to our Dogs API Slice file. Let's go down to the bottom of the file. And, we're going to do "exportconst," and let's do some empty curly braces. And now, inside of the curly braces, let's bring up our auto-complete. There should be something called "use fetch breeds query" in the middle.
JASON LENGSTORF: That is extremely cool that that just happened.
MARK ERIKSON: Where did that come from? Well, we said we had an endpoint that said "fetch breeds." And the kind of thing we're doing is a query request to fetch data. You can also do a mutation request to post update data. This automatically-generated a new React Hook by combining the names here. So --
JASON LENGSTORF: That's extremely cool that that just worked.
MARK ERIKSON: It took "fetched breeds." And combined it with "query." If we mouse over it, the types are probably going to look ugly, but we should see --
JASON LENGSTORF: It returns back the breed.
MARK ERIKSON: It takes an optional number. Like, it's got all the TypeScript stuff encoded in there.
JASON LENGSTORF: Yeah. That's really, really cool.
MARK ERIKSON: So, let's go fetch some dogs. Let's go back to our app component.
JASON LENGSTORF: I'm ready. Let's fetch some dogs.
MARK ERIKSON: Let's go import the hook.
JASON LENGSTORF: We're going to go "features." Dogs. Dogs API Slice. And I'm assuming I want the "use fetch breeds" query.
MARK ERIKSON: Yep. Let's just delete all the counter-related stuff entirely.
JASON LENGSTORF: Because I'm going to put this example up, let's just put it below.
MARK ERIKSON: So, below here -- anywhere in here -- "constemptyobject=."
JASON LENGSTORF: "Use fetch breeds" query.
MARK ERIKSON: Let's -- inside the object, let's grab "data, is fetching.
And, let's mouse over "data" for a second. (Laughter).
JASON LENGSTORF: It's beautiful. It's beautiful.
MARK ERIKSON: It's breed or undefined. So that means that, like, hey, if we haven't fetched the data yet, it's not there. So, let's be good, safe coders and fill in an empty array as the default, so "=emptyarray." And let's throw a couple things -- let's replace that paragraph. And let's throw in two things. First off, let's do "emptydiv." And let's say "number of dogs fetched."
JASON LENGSTORF: We can do "data.length."
MARK ERIKSON: Exactly. This will tell us how many came back in the request, which should be related to how many we asked for. We'll see this.
Next line down, let's do our table dance. New empty dance --
JASON LENGSTORF: Oh, we're doing a table! Let's do it! Then we've got -- is it "TH."
MARK ERIKSON: "TR" inside the header.
JASON LENGSTORF: TH?
MARK ERIKSON: Title of name. And another for "picture."
JASON LENGSTORF: Okay.
MARK ERIKSON: And then down inside of our T-body, let's open up it up. "Data.map." And we've got "breed" as our parameter.
JASON LENGSTORF: Breed.
MARK ERIKSON: We can do -- TR key is "dog.id." Breed.id. I had dog in my example earlier, so be consistent here.
JASON LENGSTORF: Look at this beautiful auto-complete we're getting here. Ah, beautiful. All right. And then -- this one, I need to make an image, right?
MARK ERIKSON: Yep. Some image in here.
JASON LENGSTORF: We'll call this one "breed.image."
MARK ERIKSON: It's "breed.image.URL" is the source.
JASON LENGSTORF: Oh, that's right. This one needs to be a URL.
MARK ERIKSON: Let's give it a height parameter of 250 for consistency.
JASON LENGSTORF: And you want a height of 250.
MARK ERIKSON: Okay. Crossing fingers...
JASON LENGSTORF: Crossing fingers. Crossing toes. Oh, beauty!
MARK ERIKSON: Dogs!
JASON LENGSTORF: Look at these dogs. We defaulted to 10. So, let's see what happens if we up this and we'll do a query for -- we want to "fetch breeds query."
MARK ERIKSON: Let's do one more thing, really fast. Let's make this dynamic.
JASON LENGSTORF: Okay.
MARK ERIKSON: So let's add another div, right above this. We'll drop a "select" in here.
JASON LENGSTORF: Okay.
MARK ERIKSON: Okay. So -- and we're actually also going to want to add -- okay. Let's go back up one. We're going to add another "use state" real fast. Same as the counter, but let's call it "num dogs" and "set num dogs."
JASON LENGSTORF: "Num dogs." Reminds me of "up dog."
MARK ERIKSON: Use state 10. Back to our "select."
JASON LENGSTORF: Back to our "select."
MARK ERIKSON: Value=numdogs, inside the "select."
Onchange -- we want the "value=numdogs" on the "select," itself. "(Earrowfunction." Because this is a string, we have to numberfy it.
We're going to make four options and their values are going to be string numbers. Let's do 5, 10, 15, 20. And, let's give them literal text descriptions of "5, 10, 15, 20."
JASON LENGSTORF: Okay. 10, 15, and 20. And screwed that up by doing what?
MARK ERIKSON: Good question.
JASON LENGSTORF: Oh, I have these -- these rogue --
MARK ERIKSON: Extra curlies.
JASON LENGSTORF: Okay. So, now we've got 10, 5.
MARK ERIKSON: Okay. So, let's -- oh, oh, there's one other thing we need to do. We need to pass the state value as the limit parameter. So, pass "numdogs" there.
JASON LENGSTORF: Okay.
MARK ERIKSON: Now, let's try it and see if it works.
JASON LENGSTORF: Number of dogs fetched is 10.
MARK ERIKSON: Okay. One more cool thing. Bring up the network tab, in the browser's Dev Tools. And, let's actually refresh the page.
JASON LENGSTORF: Refreshing.
MARK ERIKSON: Yep, refresh the page. Go to "XHR." Now go to "5." We fetched that. Now go back to 10. Wait a minute! We didn't fetch anything. Why didn't we fetch anything? Because we already have the data in cache. Go to the Redux Dev Tools. And let's look at this really fast. Click on the "State" tab on the right side. Here's how we're storing the data.
JASON LENGSTORF: Ohhhh, slick!
MARK ERIKSON: Every unique combination of endpoint, plus query params. We have it for the 10 case and 5 case. And when we switched, it said, "well, I've leave the data here a little while longer." If we had five components that all need that data, it's kind of like a reference count. Five things that need that data; four things need it; three things need it. Nobody needs it anymore. Set a timer.
JASON LENGSTORF: It just did it. It just did a clean-up, where it removed the five, because we haven't used it in a while.
Good, I love it. That's beautiful. Unfortunately, we're out of time.
Where should somebody go if they want to learn more?
MARK ERIKSON: Okay. So, right now, the Redux Toolkit Query Doc are in a preview PR that is linked from our beta release notes. The logic is basically done. We've got one or two more tweaks to do. It would be the Redux Toolkit Repo.
JASON LENGSTORF: Redux Toolkit.
MARK ERIKSON: We are hoping to have this live in Redux Toolkit Version 1.6, soon. Like, it would be nice within the next couple of weeks, at which point, it would be part of the documentation specifically. So, our goal is that existing Redux users can use this to drastically-simplify your data-fetching logic and this would be the mostly-recommended way to do data fetching, in Redux, going forward. Even people who aren't using React can still use this.
JASON LENGSTORF: Awesome. We're out of time. So, everyone, make sure you go and follow Mark on Twitter. If you've got questions, fire them out there. Go check out Redux Toolkit. I'm very excited about this. As always, this episode has been live-captioned. We've had White Coat Captioning.
While you're on the home page of the site, make sure you head over to our schedule and check out what's coming next. We've got really, really good stuff coming up. We have Marcy coming on. Ben and more. I still have to update this with all the great people who are coming.
Mark, any parting words before we call this thing "done"?
MARK ERIKSON: I'm really excited about where Redux are going. Only use Redux if it solves your problem. I think we've improved it in a way that it does solve problems more than it used to.
JASON LENGSTORF: Thank you so much for hanging out with us today. We will see you all next time.
MARK ERIKSON: Thanks, appreciate it.
Closed captioning and more are made possible by our sponsors: