React Query Full Course (With Common Patterns & Best Practices)

5k views4934 WordsCopy TextShare
Youssef Benlemlih
A complete guide to React Query (a.k.a TanStack Query), a server state management library, that solv...
Video Transcript:
tanack query is one of the most famous JavaScript libraries out there in fact one of every six react projects uses tanack query and I practically use it in all of my projects so in this video I want to give you a complete introduction to Tac query and how you can use it to bring your react application to the next level so we're going to start with the basic building blocks which are queries and mutations then we'll move to more advanced topics like how to handle query interdependencies and how to implement pagination and optimistic op dates so what is tack query tack query concentrates on one single problem managing server State imagine you have a simple application that uses some data from a server in such a scenario you will find yourself writing code to fetch data manage loading States handle errors and update the state accordingly this can quickly become complex and lead to repetitive code across your components plus if you're using the same data in multiple places in your UI you will somehow have to share it between these components without making multiple requests react query simplifies this whole process by providing a set of very powerful hooks it also takes care of caching background updates and steel data management allowing you to focus on building your application's core functionality instead of worrying about other things like race conditions so without further Ado let's get to it so this is the application that we will be building today this is a contact book so you have a list of contacts and it also has multiple Pages you can click on a contact and you can see it l the information about that contact like the phone number and the address you can also add a new contact John do and give it a number for example and an address and as you can see here we have our new created contact you can see the details and we also have data validation so if you don't feel these fields that are marked with an asterisk and click on create you will get an error we also have a server application running in the background and this error comes from the server and shown here for the user and of course you can edit a contact so you just click here and the data is loaded from the back end and you can change the first name let's just WR first and click on Save and here we can see also that the the third entry has been updated all right let's jump into the code so here I have this project where I have a back end and the front end so in the back end we have all the functionality that we need like saving contacts and getting contacts in the front end uh this is just react project and I also have a client that can make the API calls to the back end so here we have get contacts which just makes a fetch request to the back end and Returns the result it also has a typescript uh type definitions so that we have the types throughout the application so the first thing you want to do is to get the contacts and show them this list so in react query this is called a query so basically getting the data from the back end and to do that we can go inside our app and here we have our contact table so here we should display the contacts so let's just go ahead and install Tac query going to open a new terminal npm install at tx/ react query and once that's done we can go to our entry point so here this is main. TSX and this is where app is rendered and what we need to do is to just wrap this inside a query client provider and this is going to come from tac query so import from at tack SL react query so query client provider and this expects uh one prop which is the query client so let's create that client equals new query client and we're going to pass it here I'm missing a t over here so let's jump to the context table which is what is shown here and here we can query the context from the back end so to do that we can just write use Query and we need to import this from T query and use Query gets an object with the options so here we can Define how to get the data for that you can use the query function and this is just going to use the clients that we already have and call get contacts and get contacts expects a page number so we can just pass one for now and the other um option that we have to provide is the query key so the query key is used to identify this query it's also used in the cache so it should be different for each query so here we can have contacts and use Query returns an object which has a lot of useful properties and the most useful one is the data property and this contains the data that is going to be loaded from on the back end so we can display the data here so data. contacts and we can map the contacts so for each contact we can show a contact table row which is a component that I already have defined before and this receives a contact the instance of the contact and the key is going to be the ID let's also import this and now you can see that the contacts are shown if I change the page number here to two this should load the second page now we also have these two call backs here that are used so when the user clicks on the contact name or edit button that these are called These are used to open the models so I'm just going to pass them here so now when you click on this the details is shown of course this doesn't work yet we're going to implement this next and we click on edit this should also be editable and should be pre-filled with the contact data one of the best practices when using usequery is not to use it directly like this in your component but to define a custom hook so for that we can just go in our API folder and create a new file I'm going to call this hooks.
TS I'm just going to take this part here the use Query hook and here we can define a custom hook and here we can call this use contacts this is going to be a function that calls usequery let's import use Query and the client and also export it from here and now in the contact table you can use use contacts and it still works like expected a very common pattern when having a list of items is to use pagination so you get the items Page by page here we have hardcoded page two but this should be a parameter so let's see how to pass parameter to queries I'm going to start by defining the parameter so I'm just going to use a normal state and I'm just going to call this page number and I'm going to use a component from mtin and it's called pagination so here we can pass the value which is going to be the page number onchange which is called when the user selects uh a new page number so set page number and also gets a total amount of page I'm just going to say 100 for now and here in set page number we can set it to one as an initial value and when I save you can see we have our pagination down here uh of course the total is not 100 but you can get from this data we also get from the back end the total items count so it's going to be data do total pages and if we don't have data yet I'm just going to say zero now this is not working yet because we still haven't passed page number to the function making the call so let's just pass page number and I'm going to go inside use contacts and add page number as a parameter which is going to be a number now when I click you can see that actually it still doesn't work and the reason for that is because our query key is still the same for both pages in order to fix that you should actually always put all your parameters inside the query key so that way when the page number here changes the query key is going to be changed and react query is going to make a new query and now when I click on two you can see that it's loading and then loading the correct page we still didn't implement the loading State and that's actually very easy to do we can just add a is loading or is pending is actually the correct one and we can check if is pending we can return our loading state so now when I click on page two you can see that it's loading and then it shows the results page three same thing but if you click on page one again you're going to see that it's not going to be loading again because the data is already there and and it's already cached on the client now let's go ahead and add the case where we receive an error from the server so I'm going to go to the backend server and just stop it now if I reload the page you're going to see that it's going to be stuck in loading and the reason for that that it takes so long to load is because when there's an error T square is going to try three times with different intervals to check if it's possible to still get the data without showing the user an error and of course we're not showing an error because we're still not handling it here but we can go to where we use our hook and use the is error property so if error then we can return an alert with the title error loading contacts and you can see it here we can also add a button and with some text like retry and when it's onclicked we can retry the um query again here we can also grab retry or refetch it's called and pass it to the button so onclick call refetch now we wait that it's loading and now we have this button we can also color it red but now if I click retry it's going to be retrying again so let's go ahead and start the back end again and you can see that it has retried the query again so now that we have created our first query we can go ahead and also Implement loading the details of a given contact so we have here this component contact details model and it has some dummy data here and here we can also use a new custom hook for that so I'm just going to copy and paste this and this is going to be use contact without an S and it's going to receive a contact ID as a parameter which is going to be a string and I'm going to pass it here now I'm seeing that the structure of query key is a bit clashing so what we can do is to change it here and add for example list and here we can add one and you can think of this like a URL with sections or something like that so we have contacts list page number or contacts one contact ID and here we can use the client get contact so now that we have our custom hook I'm going to jump back to the model and import it and I'm going to pass here selected contact ID and we can grab the data from here this time let's import this from API Hooks and I'm getting error because this prop can be either undefined or strings so when the user didn't click on a contact yet it's going to be undefined and when the user clicks on a contact it's going to be a string so to fix this we can go to our hook and add of course either string or undefined and now we're going to have another problem that the client expects a contact ID and one way to go around this is to disable the whole query when there is no contact ID so we can just pass CL in another parameter and say that it's enabled when there is a contact ID and here we can also add an exclamation point to say that we're sure that the contact ID is defined here we're getting also this problem that data can be undefined and this is the case when the data is still loading so I'm just going to wrap everything inside data and and now when you click on it it's shown uh we still need a loading State and we can just put it inside this model so is pending if it's pending then show the spinner now let's click on another contact you can see that it's loading it's also cool to see that if you close this model and click again on the same contact is going to be shown directly because we still have the data in the cache now before moving on to mutations I just want to touch one last point which is query interdependencies so when you have a query that relies on the data from another query and the way to do this is pretty straightforward we're also going to be using this enabled flag and you're going to see how this works so in this example I have a country code here it's for Germany de and this is used to uh render this image here and I'm using this website flag CDN where you could just pass a country code and you receive an image so the challenge here is to use the phone number that we have from the user and using the phone number we can get the country from the backand and once you have that country we can pass it here to load the image so I'm going to go ahead and create another hook for that so a new hook I'm going to call this use number country code and this is going to get a phone number as an input is going to be a string or undefined and this is also going to use use Query so let's just add a query key it's going to be phone number code with the phone number as a parameter and also a query function so we're just going to be using our client function client. get country since this is either uh string or undefined we only want this to be uh active or enabled when there is a phone number now we can come back to our model and use our new hook so use number country code and here we can pass in the result from the previous query so data. phone number and from here we can grab the result this is going to also be called Data but we can rename it like this and just call it phone number data now I can come to our image and I can check if phone number data is already loaded so phone number data and image and the country code is going to be phone number data.
code and I'm also going to make it lower to just suit the API and now you can see when I click on an item wait a little bit and then it's shown and this way you can easily mix and match queries cuz it's super flexible and one last cool query feature is the select option so here we have this 100 contacts title but actually this is just hardcoded and it's hardcoded here and actually we do need to know how many contact items we have from the back end and for that we can reuse the same endpoint as before which is this use contacts now there are two ways we can use this in our component we can just import use contacts directly in our component like we did before and check how many contacts we have or we can use the select option and this select option is basically just a function that receive the data and applies a transformation to it so here we can call data do total contacts which is a number the advantage of using select is that it's very performant so when the output of the select function doesn't change the component doesn't rerender and here we're using use contacts in two different places so what we can do is to have a select option that we can pass it to use contacts and this is going to be a function that Rees the data which is get contact response and returns something like a new type I'm going to call this T and we can use generics for this so we can Define the type T and then we can pass in the select here now this is actually optional so we don't always have to pass in select and by default we want this T to be defined as the get contact response in the case that we don't provide a select option so if we go to our contact table we don't have any typescript error but now when we go to the app component and import use contacts I'm going to say page one because it doesn't matter and here we can call data data. total contacts and now that we have the number of contacts that we have we can also display it here let's reload the page I'm sure we're going to have it undefined and then contacts so here we can check if we have data and if not we can just just have an empty string so I'm going to reload the page and it looks pretty good okay so we are done with our queries let's now move to mutations first thing we want to do is to implement this add contact formul so when the user fills this contact and clicks on create we want to have some kind of loading State here and then the contact is created in the background in the server so I'm going to jump to the create new contact model and and here as you can see we already have a use state that has our form State first name last name phone number and the address and these are pass to our text input so when they change the value is saved inside the state and here I'm going to create a new hook so use create contact let's just do it directly inside our hooks file so const use create contact it's going to be a function and this is going to call use mute mutation so mutation is just an operation that changes data so in this case creating a contact but it could also be editing a contact or deleting a contact so I'm calling use mutation and this just needs a mutation function very similar to the query function and here we can call our client do create contact and this expects a parameter which is create contact request so I'm just going to added here to the mutation F function and pass the contact to the function I'm also going to import this so now we have this use create contact and we can use it inside our component so use create contact and this is going to return an object again and it has a field called mutate which is the function that we can call to perform the operation and it also has the fields is pending which we can also use in the button so we have this button with an on click listener on Save click and when this is called we're going to call mutate with the given uh state so here it's going to be form State and when it's pending I'm also going to pass this to the button to show that it is pending so loading is equal is pending so let's click on add contact and I'm just going to write first name last name some phone number and some address and when I click on create so we can see that it's loading but we don't get any feedback that it has been successful so the way we can do that is head over to our custom hook and pass in a function on success and the use mutation hook also has an option for that on success and this is going to be passed here and this is just going to be a normal function and I'm also going to make this optional because you don't have to always listen for this event and here we can pass in our function and what I'm going to do here is just to close this model now when you create a new user and let's add a phone number and click on create you can see it's loading and once it's created the model is closed but as you can see we also have a new problem now which is that we cannot see the contact that we just created so if I reload the page then you can see it and the problem is that react query doesn't know that this data has changed so what we can do here is that when the query is successful we can call the special function from Tas query to invalidate the query that has changed so here I'm going to wrap everything inside these curly brackets and return the mutation so it's just like before but now I'm going to use use Query client Hook from Tas query query client and this has a method that we can call in on success so I'm going to call the past parameter first and we can call query clients. invalidate query and here we can pass in in a query key so in this case we can just pass in contacts and this needs to be an object so query key is contacts so what this does is that it invalidates all queries that start with contacts and this is going to be the use contact and use contacts because they all start with contacts the number country code is not going to be invalidated so it stays the same now what does invalidate query actually do so when you invalidate a query it just means that next time that you're using that query tquery will make the request to the back end and not use the cache so now let's try creating a new contact I'm going to call this new contact pass in a phone number click on Create and now you see that it also Updates this list now what happens when the user doesn't fill all the fields so here I didn't put in any front end validation so the fields are also required by the backend so if you don't fill the fields and click on create there's going to be some kind of error from the back end but we need to somehow show it to the user and the way we can do this is by grabbing the attribute error and use it in the front end to display our errors so I'm just going to come here to the end before we have our buttons and check if we have an error then let's just show error that message and see what happens I'm going to click on Create and you can see first name is required now this error comes from the back end and now it's shown to the user which is a really cool thing now instead of just showing some plain text we can also show an alert from M time and now it's very clear that it has been an error so let's fill in the first name Usef new and click on create you get an error last name is required and to write my last name if I click on create you get a new error phone number is required of course a best practice would be to check this on the front end but in case you get any kind of error from the back end is at least shown to the user so this is really cool user experience one cool feature that you can also Implement with tack query is optimistic updates so what are optimistic updates optimistic updates means we don't wait for the server to respond before showing feedback to the user for example when a user creates a new contact we can already show it in the given list or when the user has edited a contact and closed this model we can already show the new contact information here so let's go ahead and Implement that there is this component which is this edit model and it's implemented very analogous to the create new cont cont it just uses a query to set the initial value of the model and here what we want to implement is that when the user clicks on edit changes something clicks on Save and then closes the model then he should already be able to see the new name and the way we want to do that is to go to this mutation hook and here we have the mutation where we edit the contact and what we need to do is to provide a mutation key that differentiates this mutation from others and here I'm just going to call this it contact and of course we need to be sure that we're talking about the same contact so I'm also going to pass in a contact ID let's add this to our parameters so now that we have the mutation key we can go ahead and create a new custom hook and let's just call this use optimistic contact name and this is going to receive a contact ID which is a string and this hooks just Returns the new contact name that the user wrote inside the edit model so for this we can use use mutation state which is another Hook from tac query and we can pass in a filters and here we can say that we want the mutation key to be equal to to the one that we have in the other mutation and I also want to check that the state is pending so it's still happening and this returns a list of mutations these are all the mutations that fit this criteria the mutation key and status is pending and here we can check if we have a first element so first mutation it's going to be mutations at zero and if we have one then we can return the first mutation that variables and this is what we have passed here inside the mutation uh function this is going to be a contact let's just create a variable for that contact and then we can return contact.
first name plus contact. last name and of course if it's not the case we can just return undefined and now we can use this hook inside our table component so we have the contact table row and inside of here we can have this use optimistic contact name and pass in the contact. ID so we can make a variable out of this optimistic contact name I think we need to export this function so let's just go to the hooks and Export and now we can import it here and so now when we have this optimistic contact name we can display it here so let's check if we have optimistic contact name then display it otherwise we can use the contact first name plus the contact.
Copyright © 2025. Made with ♥ in London by YTScribe.com