Intermediate React, v5

Redux Toolkit Query & Middleware

Brian Holt

Brian Holt

SQLite Cloud
Intermediate React, v5

Check out a free preview of the full Intermediate React, v5 course

The "Redux Toolkit Query & Middleware" Lesson is part of the full, Intermediate React, v5 course featured in this preview video. Here's what you'd learn in this lesson:

Brian adds the RTK query to the petApiService which allows all the necessary endpoints to be built around a base URL. Parameters for each endpoint can be specified as well. A middleware is also added which allows for additional caching of reponses directly in the Redux store.


Transcript from the "Redux Toolkit Query & Middleware" Lesson

>> The next thing that you and I are gonna do, this application uses React Query, we've been doing that for a while. There's a thing specifically called React Toolkit Query, which is inspired by React Query. I'm gonna say if you're doing Redux Toolkit, just go full into it and also use React Toolkit Query.

So we're going to refactor our React Toolkit, sorry, React Query to Redux Toolkit Query. So first things first, let's make a new file here and call it petApiService.js. Here, you're going to import createApi and fetchBaseQuery from Redux Toolkit. You're going to export const petApi is assigned createApi. If you give it a reducer path, we're just going to call it petApi.

It uses all these names internally, so that I can keep track on things and cache them correctly. It will be useful once we get into the Redux dev tool, so you'll actually be able to follow along of where things are going. So it's good to give this a good name, a descriptive name.

Okay, the baseQuery is going to be a fetchBaseQuery. The baseUrl is going to be And then you're gonna even some endpoints. So if you have a bunch of things that are based off of one baseQuery, as in one baseUrl, you just make those into individual endpoints. And then if you have two different API's that you're calling, you'll make another service, right?

So in this particular case, we only have one service, one baseUrl here, so we're just gonna have one service. But imagine if we have our API endpoint, and we also have the Spotify endpoint, right? We would have two different API services that we would create. Okay, and then here, I'm just gonna describe all of the endpoints that I'm about to hit.

So this is a function that takes in a builder and it returns an object. We're gonna make one called getPet, which is going to be builder.query, Another object here, and the query is going to be id, And it returns an object. url: "pets", right, cuz it's the name of the actual path to the petApi, that is.

And the params, it just takes in the id. So just to be super clear of what's happening here, if I call the getPet API, it's going to go to this baseUrl, slash whatever the hell is here, pets with the query string of the id, id equals id. Little indirection there, I know this is pretty dense, but that's what's happening here.

And then here, I can give it a transform response. So this is actually the raw response back from the API. All I wanna store and use is the response.pets(0). So if I'm requesting a specific pet, this is going to take the entire response that it gets back from the API and it's gonna give me back just the first pet, that pet array.

So then now I don't have to do all of the, if this exists, give me this kind of logic, it's gonna be the pet, you don't have to worry too much about it. Okay, export default or export const rather, (useGetPetQuery) = petApi. Yeah, Redux toolkit/query/react. Sorry, you have to give it a little bit more specific what you're looking for there.

All right, this one was weird to me as well. Where the hell does useGetPetQuery come from? It's generated by this createApi method, right? So it takes the name of my query here, which is getPet, and then it puts use in the front, cuz this is a hook, right?

It makes a custom hook for you. It puts whatever you call this. So if I call this use getPetLol, right, I'd have to put getPetLol there, right? That's how the name is getting there. And then query, it attaches that on the end for you, so that you know that it's a query.

Okay, is that sufficiently weird for you? It's sufficiently weird for me, so here we are. Okay, let's go put this into the store now. All right, so two things we need to put in here. One, we're gonna put in the pet, petAPI from here, [petApi.reducerPath]:petApi.reducer. Why this? The answer is, the docs told me to, so I'm showing you what the docs told me to do.

But really what they're trying to do here, if we go back to our, What did I call it? PetApi service, yeah, petApiService, it's just this, right? So it's actually calling it petApi, it's just making sure that it's internally consistent, right? So this petApi is exactly what it's putting in store, in fact to be incredibly clear, store, and this is petApiService.

So we have petApi, we have the reducer path here, right? This is just saying, hey, make sure that whatever this is putting out from here is the same over here. It's being very explicit about that. Could you put petApi right here? Yeah, I mean, you could, it's literally exactly what this is, right?

But it's nice to have it guarantee be consistent, cuz now if I decide later that, yeah, you know what, I wanted to call this petApiVerCool, right, I don't have to go change it in two places, it just changes automatically. Okay, make sense? One more thing here. This is kind of an optional step, kind of not.

We're gonna give it a middleware, and I can get rid of this, so that you can see everything, middleware:(getDefault, Middleware). This is gonna be a function, and we just want it to return implicitly get. DefaultMiddleware, call that function .concat(petApi.middleware) So what's going on with this? Redux has this concept of what's called a middleware, which is basically, I dispatch an action to the store.

I can intercept that in a middleware and do something about it. You can imagine maybe I'm dispatching it in one object and I wanna transform it into a different object. A middleware could do that. You can introduce things like you want your Redux to be able to handle observables, right?

So you can catch an observable, handle it, and then from there, dispatch an action to the store. We're not gonna do anything like that, in general, I gonna tell you to not really worry too much about middlewares. The only thing that might be kind of useful is if you want to log what was happening to your Redux to maybe a server, you could do that through a middleware.

Kind of weird, but you could do it. What are we doing here? We are putting, petApi has this thing called a middleware, into our Redux here. What's cool about that is now it's going to cache everything for us. So you know how React Query was caching everything for us as we were making requests.

This is allowing that to happen here with React toolkit. But you have to put the middleware in here. If I don't put this middleware in here, all of this stuff will work, but I don't think any of the caching will. Actually, I don't think that's true. I think the caching actually still does work, but there's other things like retries.

It augments the ability of React Toolkit. In other words, just do it [LAUGH]. Okay, we're really close to getting this to work now. It's all ready, we just need to go use it. So please head over to Details.jsx. Remove the useQuery import up here. Remove fetchPet, And we're going to import the useGetPetQuery from a petApi service.

We're going to delete this thing, the results here. And we're just gonna say const isLoading data: pet = useGetPetQuery with the id And where do I declare pet? Down here. And then you can drop this line right here. Okay, and then here where it says results.isLoading, just delete that, so it just uses the isLoading from that.

All right, that is it. So now, this is going to make requests through our petApi service using RTK. When I say RTK, Redux Toolkit, right, RTK. That's what everyone calls it. You'll see it always written like that, RTK, Redux Toolkit. We're using RTK to call that petApi service, which will get us our pet for us.

Okay, so looks like we broke. What happened? inject.endpoints is not a function. createApi, fetchBaseQuery, export const petApi, createApi. reducerPath looks good. baseQuery: fetchBaseQuery, baseUrL, yeah, that looks all good to me. endPoints, I think this is a lowercase p. That was it. Okay, sorry, lowercase p, line six here, endpoints.

Okay, any questions about that? Can you spell? Apparently I cannot. Same sort of thing here, if I search for bird, it'll go get that. But if I go back and search for blank, now, it's not gonna load, it's just gonna instantly give it back to me, cuz it's gonna be cached directly in my Redux.

Cool, so what's nice now is, if I go back to my petApiService here, adding more endpoints to this is very simple, right? Cuz I already have all of the base machinery in place. All I have to do if I wanted to add another one is just say getBreeds, And basically do the exact same pattern over again, builder.

GetBreeds here, sorry, builder.query, give it an object. Query: animal, And the url is going to be breeds. And the params is going to be animal, like that. And then you're gonna give it a transform response as well, and that is going to be response is back to response dot breeds.

Boom, now we have a getBreeds function here. And here, you just put down here useGetBreedsQuery. How easy was that to create a whole custom hook for one API response? Pretty compelling. We'll do search, Builder.query. So the query, I'm gonna to do this a little bit different. We're actually gonna take an entire function here, so query, and that's gonna be a function that takes in {animal, location, breed}.

Like I said, actually, it's the same, never mind. And that's gonna give back url of pets, params of, {animal, location, breed}. And then the transform response of response back to response.pets. And then now we can say, useSearchQuery. And there you go. Now, we have our entire API here just described like this.

So let's go to useBreedList. We can get rid of this, we can get rid of everything here at the top. And you're going to import useGetBreeds, Query. There you go. And get rid of that. And delete this line, const results, const { data: breeds, IsLoading } = useGetBreedsQuery animal, And I have a skip if you have no animal, right?

So if someone is trying to call useBreedList without an animal, you just wanna skip the query altogether, right? So you can give it this skip parameter, right? Which allows you to say, hey, if you got no animal back, then don't make any requests to the API. You don't need to, which I think is pretty cool as well.

Then you just say, if no animal return, It'll say empty array here and loaded. Otherwise, return Breeds. And then, You'd probably wanna have more robust statuses, but for now, we're just gonna say, if it's loading, loading, otherwise loaded, Right? More or less the same as what we had before using React Query, but now we're using our API service.

So let's go make sure that this works the way that we want it to. And if I say there's no animal there, but if I put dog, we get this back, that looks good. If I go to cat, that's the back. And then if I go back to dog, everything works, cool.

One more, let's go do SearchParams.jsx, and we are going to, Get rid of useQuery up here. We're going to import useSearchQuery, and then the useQuery call down here. We're just gonna say, const data: pets = useSearchQuery with SearchParams. That's it, that's all we need to do here. And we can get rid of that.

And get rid of all my console logging as well. I mean, that got even simpler, right? So I I don't know, this is probably the most compelling part about Redux toolkit. These search custom hooks that it generates for you are really quite nice to use. What is this mad about?

Is it mad about something? Yeah, we don't need fetchSearch anymore. All right, so, what did we do now?
>> You have to define pets. You have to reassign pets.
>> Yeah. If there's nothing there, you have to say, pets = pets. Cuz you need it to be an empty array if it's, So all I did here is I changed const to let.

And then if pets is an empty array, Or if pets doesn't exist, then make it an empty array because this always assumes that they're down here, that this is gonna be empty array.

Learn Straight from the Experts Who Shape the Modern Web

  • In-depth Courses
  • Industry Leading Experts
  • Learning Paths
  • Live Interactive Workshops
Get Unlimited Access Now