Advanced Redux with Redux Toolkit Implementing User Slice Exercise
Transcript from the "Implementing User Slice Exercise" Lesson
>> So I help you set up a task, your job is to set up a user. And then when we come back, we'll talk a little bit about like, so how do we deal like the question that I got earlier from who, these slices seem tightly bound to their own actions.
[00:00:10] I showed a little bit what actually implemented to see how do we deal with actions from other reducers and coordinate all of that. And then we'll do async, and it'll be fun. All right, let's do a thing called creating a user slice, the solution, hypothetically. All right, so we had a task once, that can kinda be our inspiration here, and we can use that to figure out how we would do the same basic idea.
[00:00:41] And then like I said before, we'll wire them together and do some things there. And then we will figure out how to make them talk and then do some asynchronous stuff as well, cuz that's always fun. All right, so we have a tasks-slice. Again, the way to kinda think about this is for you to kind of model in your application for each thing, for each noun, if you will, we'll have both of these in place.
[00:01:07] So a lot of this is somewhere from before, last time, we kinda broke it up into pieces, let's see it together in one kind of piece. So one thing that we can do is, if I wanted to pull in the preloaded data, I can do that. And then we'll talk about APIs in a little bit.
[00:01:24] But let's pull in the preloaded data. We'll say, ../, it's an API, I guess, right? So we have that in place. Now, that's not gonna do us too much good just yet, but we'll have that for now. And then we'll say that our initialState as we saw before.
[00:01:50] I'm gonna follow the same pattern as I followed last time, which is this idea of a user. And curious if I accidentally deleted someone when I was in a file area, let's see. User, we got a user. Again, if I write for the seventh time today, write constant set of type, we will find out that things don't work as expected.
[00:02:19] It's like you're using types as a value, I was. Awesome, so we've got that in place, and there's some other pieces that we'll do, but let's start with the slice. So I'll say const usersSlice, and actually, this is really should be our UserState or UsersState. And then we do wanna make mixing two thoughts together, right?
[00:02:45] This will be our userState.
>> Type, const?
>> I know you want type instead of const.
>> I want const this time. This is the type, this will give me the value for the initialState. Yeah, I'm gonna say, of this type, right? So we kind of separated them.
[00:03:02] Theoretically, slice is a great point actually, the slice will take the shape of the initialState and use that as the type. So I could have just actually defined an initialState and said that it was a user's array. It's a little trickier than an object. And then if I ever I do need to know the shape of just this piece sometimes, I am gonna have to break it out and export it.
[00:03:26] So I as a habit at this point just separate them, you could theoretically just make an initialState, but with that array, sometimes it can be a little tricky. Take the entities, and that will be empty array. Now, it will know that that is an array of users because we have assigned it to this state, right?
[00:03:42] But great point. So const usersSlice, and that's gonna be my createSlice. CreateSlice, and that will take a name, pretty easy in this case, users. It'll take an initialState, and then the other thing that it requires is reducers. And now, that reducers could be an empty object, you just have to hand it something in that case.
[00:04:14] So we have that kind of in place, and so now, we're gonna have the same basic actions here for now. addUser, and that will take a state. We're gonna need that draft user ID in a second, but we'll start with just this. Take a payload action, It doesn't figure out user type unless you make it a full object.
[00:04:40] We'll have that, we'll start out with just a user, I'll swap that out in a second. And so we have that in place. And then here we'll just say, actually, let's handle it cuz I'm gonna need that createUser function at this point. So say createUser, that will take this idea of a draft user.
[00:04:56] Luckily, this is why I made the helper before, I don't have to think about this anymore. What are the things that I care about on a user? They have real names and alter egos, I probably want both of those, right? And so we'll say that a DraftUser is that RequireOnly, A type DraftUser RequireOnly.
[00:05:24] And off of the user object, we'll take the, Autocomplete for the win. RealName and alterEgo. And so we have that, that's gonna take, A DraftUser, And it will return as a real user, or at least a valid user, how about that? I'm getting yelled at cuz it's not doing that correctly.
[00:05:52] So here we'll just say, It'll pull in nanoid as we did before, and then everything else. And the other reason to kinda do it in this order is that way, with object spreads, if there is an ID in that DraftUser, it will override the one I just made.
[00:06:16] So either way, if for some reason I do have the ID, this still works but it will guarantee that we have an ID, awesome. And so we'll do that, and we'll say draftUser for the rest of this. Okay, and it's angry with me. Why are you angry? Cuz it also expects, Unclear, let's see.
[00:06:40] What are you angry about? That one, it wants something. So we can say, okay, this is good, I'm okay with this, cuz actually, so tasks, the relationship, it's like string or undefined. I actually never wanna deal with that situation ever again in my life. So I do wanna say that the tasks are an empty array.
[00:07:05] I don't ever wanna deal with the fact that an array could be undefined. If I didn't pass one in, that's totally cool. Make an empty array, it will be the correct type because the user type is based on that. In this case, that is an array of strings.
[00:07:19] And then if I did have that information for some reason, if I was creating a user based on a task, you've seen forms where it's like you can do one thing based on the other thing. This will all work just as expected. Awesome, and so we've got that in place.
[00:07:37] And now we can do, Const user, and we'll say, With that action.payload. And one of the things I really like is I can be mostly confident about all of this cuz the type system has my back. And so we'll say, unshift, our user on there. And remove is much like we saw before, where we say, cool, just got to state an action.
[00:08:11] And that's the PayloadAction of, we'll follow the same pattern that we did last time, which is in this case, it's gonna be, finish that so I don't get yelled at in the meantime, the ID. Either choice, I can argue for or against. Doing two different things in two different slices is the only truly wrong option in this case.
[00:08:34] So we've got that in place. And now, again, for this, we had to find the index and then slice it out, not my favorite. I could see myself writing a helper method for this if I had to do it probably any more than this. The same way for other things, I've done that as well.
[00:08:58] So we've got this and we can say findIndex, we can take the user. The user with the ID that equals that. Cool, we didn't put an ID on there this time. State.entities, awesome, and then we splice that one right out. And so this is where I put in the entities cuz you can modify things.
[00:09:27] If state was an array, it's a draft, then you destroy the draft that was there, so that's why it's nice to have the array kinda as a child in that case. So we've got that in place, and then we say state.entities.splice index and 1 past it. Awesome, and that's now we just export stuff.
[00:09:49] And now, I have a working, we'll make this a default export. I do things that I need to do, and I say them out loud now before I forget, and I'll answer your question. I need to add it to the store, and then, yeah, otherwise all this is for nothing, cuz there will be nothing listening to those actions later.
[00:10:09] Yeah, what's your question?
>> I should have asked this earlier, but-
>> I'm here for it.
>> I thought you said you like to create the IDs in the reducer, right?
>> I mean, I am, this function gets called in the reducer.
>> Right there, okay.
>> It's sitting up here to keep this nice and neat. It's happening there-
>> It's just written slightly above mostly so I can export it, so I can write a unit test, or more importantly, if I can do a spoiler alert, may I spoil?
[00:10:41] So that I can use it in my test to create users, right? So I can make it an initialState that has this stuff in it, right? And I don't have to like call add, or if I'm testing add, calling ad to set up the state to test add, feels bad.
[00:10:55] So we got that in place. I'll do the default export first, degault, usersSlice. And we got that, and then we'll do the other pieces. In the same fashion I did before, we've got this export const usersReducer. And for those of you all who have done the make six different files, I'm typing obviously, but I'm talking while I'm typing.
[00:11:30] And it is definitely a lot more delightful than doing it in Vanilla Redux, and that's not even the free TypeScript support. So yeah, [LAUGH] it's the smiling and nodding, right? The other thing that I have learned the hard way is I wanted to call these just add and remove.
[00:11:51] But then again, auto import being your friend, it is worth it to make it a little more clear what they are. What makes sense in one file doesn't always make sense when you have the power of auto import to pull stuff in for you. And getting the wrong one or just not having a useful list there is not fun.
[00:12:14] Awesome, And so that's a full set of actions in reducers I can use anywhere. I'm gonna pull them into my store, and that's as simple as doing this. Boom, now I have full support. And my type was updated for me cuz it's based on the result of getState, and we are good to go.
[00:12:38] We can go put it somewhere in our UI to verify that reality exists. One thing that I should do is I went through the work of pulling in this default data. And even though this is a JSON file, if you've used other type systems, you know that, if it's not really of that class or whatever.
[00:13:03] TypeScript goes, like, yo, it's got everything that a user has in here cuz I literally just saved it from the mock API. And so even though it's a JSON file, even though it's just data, it fits the characteristics of a user. So it is fair game to be put into the store, I don't have to think about it, everything just works.
[00:13:23] Everything is good, life is easy. All right, so in the users-list, I got to put this in the UI, this is just a stupid hard coded thing in here, useAppSelector, and that'll be state. And again, watch this, cuz it makes me happy every time. Look, it knows that users exists, right?
[00:13:45] It knows when to pull in the entities. Now, it's happy, all right? This is actually not shown in the UI originally cuz there was nothing to show. But look, it's almost like I knew that, Except I didn't import it. Awesome, and we should see it on the page.
[00:14:12] There they are, right? So two things we wanna learn before we go into the async stuff is I wanna learn how to make two of these things talk to each other. The other thing I wanna learn is alluded to how easy this stuff is to unit test. So let's spend a little bit time looking at what testing looks like for a second.
[00:14:30] And then we'll look at how you have two different models talk to each other. And then we'll start to look at some of, we're gonna focus mostly on just the built-in subterranean Redux Toolkit. I'm gonna talk a little bit about RTK query, but we're gonna focus mostly on the Redux Toolkit part because, well, I'll talk about why when we get to it, but we'll take a look at that.
[00:14:49] But first, let's talk about, I'm gonna load up the task just so I have a UI that looks like something. And I'm gonna push this up, and then two minutes later, we'll talk about unit testing reducers and slicers. So let's go and add that in. And can I show you all something that I just learned literally this morning?
[00:15:14] Unused imports, go away, and it just removes all of them. But yeah, we've got that, and now we want to just add the tasks in there. And that's the same thing I did with that data just so I have something to show in the UI that I had originally.
[00:15:35] Data.json, and we'll pop that into my initialState. It's nice that I saw that error from the red squiggly line. Awesome, so I've just loaded in that data. So I'm gonna push this up, and then we're gonna switch gears a little bit to talking about unit testing and see some examples there about some strategies around there.
[00:15:57] We'll talk about some strategies around things that affect multiple reducers. And then like I said before, we'll talk about how to deal with asynchronous actions in Redux very, very easily.