TanStack Query - How to become a React Query God
By Austin Davis
Summary
Topics Covered
- QueryClient Powers All TanStack Hooks
- Query Keys Cache Dynamic Parameters
- Enabled Flag Enables Conditional Queries
- useSuspenseQuery Guarantees Defined Data
- Dependent Queries Chain with Enabled
Full Transcript
I have a query solution in react that gets rid of this garbage no more spamming use effects and having multiple different use states to manage a single query this solution is what the vast majority of professional grade apps in
react use it will make you go from writing querying like this to this the solution that I'm talking about is of course tanat query which most people just know as react query and in this
video I'm going to show you exactly how to use it as fast and efficiently as I can pstat query is a querying and State Management library that makes querying super easy and efficient in a super short summary what it essentially does
is provides hooks that have reactive State loading State error State refetch and capability all builts into one hook under the hood it uses a query client that keeps track of all the queries in your app and it will automatically cash
queries and use that cash to make queries as fast and efficient as possible I could talk about all the features for 30 minutes but let's cut right to the Chase and I'll show you exactly how to use it here's how to get started with tanack query to get it into
your project go into the terminal and run the command npmi at tanack SL react dqu once we've run that command we're good to start adding tanack query to our code first and foremost before we can
actually do any querying we need to wrap our app in a query client query client is the backbone of tanack query and without it any tan stack methods we call would have zero context on how to fetch and retrieve data so what we always do
first is initialize the query client typically within the main. TSX or app.
TSX file I'll do mine in main where we're actually rendering our app up here I'll just go ahead and write con query client equals new query client just like this and we'll make sure that's imported
from tanack and it's important that this query client exists outside of an actual react component next we want to wrap our app with query client provider meaning the app will be between the opening and
closing tag of this provider so we're going to spell it out like this query client provider again import this from tanc go ahead and give it an opening and a closing tag and we'll wrap our app in
it just like this go and save it to get some formatting and you'll see we have an error here it says we need to do a client and that's actually where we're going to pass in the client that we just made so we're going to give it a client
prop just like this where we pass in query client and that's all that it takes for initializing the query client now anywhere inside the app if we call any tan stack functions we know that we're referencing this query client
right here because we wrapped it within this provider now that we have the setup done we're ready to get started the core of tanack query is the use of the use Query hook it's how we actually query
for data how we'd use it is pretty simple we'd make a variable I'll just call it query and we would set it equal to use Query this would be a function again imported from tanac it takes in
one primary argument that is an object this object needs two properties to work properly the first property is a query key which we can just write as query key like this and query key is going to be
an array the query key is used for refetch data and caching data so it's super important that we have this this key can be anything but above all it needs to be unique that way we know which query that we are referencing if
it is not unique let's say another query shares the exact same key the query client is not going to be able to distinguish between the two of them so let's say this query in particular is fetching some to-do items we could just
pass in a string called to-dos an R here as our query key we can change this later but this will suffice for now the second property of this object should be a query function denoted as query FN
this is a function that will run whenever we run the query with this key this is the function where you would put your API call meaning your fetch axios or whatever else you're using to get your data I'll just use fetch in my
example and we'll actually extract this out into its own function to make it easier to read so just down here at the bottom of the file I'll make a function I'm going to call it const get todos and
this will be an async function and inside this function we're going to do something pretty simple I'm going to make a variable for a fetch call I'm just going to call it const response equals a weit Fetch and I'm going to
passing this link here that will act as our mock API if you go to the URL of the link that we just pasted in you can see that it generates some Json data it's just an array of some to-do objects that just have some random stuff in them that
we're going to use to test our querying if you're familiar with data querying you know that at the most basic level this is generally how we do it before we get out of this function all we're going to do is we're going to return await
response. Json that way it gets parsed
response. Json that way it gets parsed as Json data and now this is going to be the query function that we're going to use for the used query hook so this function is complete and up here for our
query function we're just going to plug in get todos just like this and just make sure we're not actually calling this function here so we don't want parentheses on it like this we just want to pass in the function straight up now
our used query Hook is all good to go but your next question is probably what is this actually doing and more importantly what actually is this query being assigned to we're assigning it to this use Query with all this stuff
passed into it but what does use Query actually return this is where you realize Tan St is legit if we head over to the tan stack docs and look at the documents for use Query this stuff right
here is everything that is returned from use Query that we could extract in our code if we wanted to data right here is the most important thing that's what actually contains our pared response we
also have helper booleans like is error is fetching is loading is paused is pending you get the picture point is there's a ton of stuff here and we could extract as much or as little as we
actually needed as I said data is the most important return from use Query so in our code here instead of just making this variable here called query what we can actually do is destructure this and
we can get data out of it just like this and just to give you a little cheat code here if we don't have anything in our object and we do control space and vs code to get the intellisense we can get all the things that we could possibly
pull out of used query data being one of them but as you can see here we can see everything that we just saw on the docs but we will choose to pull out data let's see what we actually got for our data so in our jsx here instead of
saying something blah blah we're going to go in here we're just going to json.
stringify and pass in data go ahead and save our page if you look over on the right here you can see we got all the to-do data all on our page here just to make this not so overwhelming I'm going
to go into our data here and just take the first couple of entries I'll just do do slice 010 just so we can grab the first 10 entries so you can see them on the page here now your next question is inevitably going to be okay that's cool
and all but what's so special about tanack well I'm glad you asked let's actually take a minute to explore some of the things we could pull out of Ed query looking at our intelligence here
hm I want to add a loading spinner on this page now whenever this data is loading into the page so I'm going to find is pending here pull that out of use Query and then let's do something with this I have this loading component
here in my app so let's actually use this as pending Boolean that we just pulled out of use Query we'll say if it's pending then we want to display that loading spinner and if it's not pending meaning that data is loaded in
we will just display our data if I go ahead and save this and then refresh the page you can see for a very quick Split Second the loading spinner appears in the page but it goes away so quickly because the query happens so fast to
simulate a fetch taking a little bit of time I just added this timeout here to make sure it waits an extra second before the query is resolved now if I go ahead and refresh the page you can see the loading spinner appears in the page
until the data is done and loaded in and then it displays let's say that we now want some way to refetch this query I've made this button right down here that we want to refresh the query whenever we
click it I don't need to write a bunch of logic inside this on click to refetch it all that I need to do is go into our use Query get the control space for intellisense find the refetch function
which is down here extract that out of use Query and then in our on click here all we have to do is pass that in as a function and refetch the query go ahead
and save this here now if I click the refetch button our query is going to refetch itself if you notice whenever I click the refetch button we don't actually see it happen with the loading spinner for the same reason that we
don't see it every time I would save this file in the page hot reloads it's because this query right here that we're doing is cached using this query key we've already queried with this key
before so that tells tan stack when we call it again that it's already in the cache meaning when we click refetch all it has to do is look in the cash and load in the data hence why it's instant and we don't see a loading spinner I'm
sure you notice that when you're looking through the thing we can pull out of use Query there were other booleans like is loading and is fetching if we wanted to see it every time whether it's cash or not we could do something like is
fetching instead of is pending so we can say is fetching like this save it and now every time I save the file or every time I click the refetch button it's going to show the loading spinner
because it is fetching but for our purposes we're going to use is pending the primary difference is that is fetching is whenever the query function is running at all is pending if as if there's no cache data and is loading
which I'm sure you've seen as well is whenever it is executing for the very first time usually I like to use is pending in most scenarios occasionally I will switch to using is fetching or is
loading but it's important to know the differences we've already seen a couple of these things used out of used query that being data is pending refetch what if there's an error well luckily we can actually pull an error out of here as
well and we can just look in our query say if there's an error like going down here just saying if error then we can alert and say you know know something
went wrong just like that if I go on our fetch down here and I mess up this URL to make it something that doesn't actually exist I go and save it and refresh the page it's going to have this aor here and it might take a second
eventually it's going to error out and it's going to say something went wrong there you go hit an error super easy way to do error catching so I'll go ahead and just control Z this take it back to what it was and change it back to normal
refresh the page to go and fix itself and we have our data back I could go on and on and on about how to use all these different utilities returned from use Query but the point is everything we could possibly want in a query from the
data itself to the Loading functionality to error catching is all contained in one hook this use Query hook no more spamming use effects and having five pieces of state that all manage the
querying it's all Consolidated into one hook and it does caching built in to make it fast and efficient one very common use case for querying is needing to pass in some parameters to the query
for example if I have an endpoint that needs an ID and that ID might change depending on what we're doing a great example would be in this mock API over here this comments endpoint that just
takes in a post ID so it can fetch all comments that correspond to that particular post but let's say in my app that I have an ID stored in a piece of react State and it just defaults to the
number one I also have this button down here that says increment ID every time I click it we'll just increment the ID by one now what I want to do is plug this ID into our query the most important
thing that you do is pass this ID into the query key right here to do that we can simply add to this query key array by adding a comma here and we're going to add ID to the end of it and this step
is super important because if I don't pass in this ID to the query key tan stack won't be able to accurately cache the query for example let's say I have two scenarios on this page one where ID
is one and one where ID is two which could very well happen because we have a use state that increments them if this ID isn't part of our query key here those queries will be identical in the
eyes of the query client which means our queries won't behave as expected and is going to result in some problems but what adding this ID does is tells it that if ID is one that that query is
going to be a different query than if the ID is two and that differentiates them so it's super important that if you have variables on a query key like this that are going to change what the result
is that you have that variable as part of the query key the next most important thing is that you actually pass the parameters through to your query function so in our case we want to get it to our get todos function I've
changed this query down here this get todos to now accept an ID argument and then it just adds that ID to the end of our fetch here so that we're mimicking the exact fetch on this mock API right
here so now this should match so if we go up here we're going to want to pass that ID into our get todos we don't want to actually call this function manually we want to pass it through for T stack
to use so if we just add the ID like this to the end of the function that's not going to work and you see we're going to get a typescript error here CU this isn't valid we need to add the parenthesis in front of here make an
arrow function just like this so we're passing in the function definition not a function call now that we have this it should behave as intended so if I go ahead and save this file here you can see it's loading in some data and it
just loaded in some comments from a particular post that post being ID number one if I click increment ID notice how it's going to load again load in more posts that have a post ID of two
and I could do this any number of times and each time it's going to load in some unique data cuz each time we're incrementing the ID we're hitting a unique end point and I can actually show you real quick what happens if we don't
add that ID to the query key so if I take that away go and save this see that we have some post here it just randomly jumped to post five for some reason if I
increment nothing happens because tan stack has no way of differentiating which ID is which if we're not passing into the key but if we go ahead and pass it back in here save the page it's going
to hop back on the correct Behavior starting at 10 going to 11 and so forth because now with the query key it can figure out which query is which and this is exactly how you pass in parameters to your queries it's always going to look
something like this or at least it should in most use cases if you're familiar with the basic hook rules you know that you cannot call Hooks and react conditionally so conditional querying is not made easy with vanilla
react one really cool thing about the used query Hook is that you can conditionally call queries somewhat for example if if I have a Boolean react State appear here that is just on like an onoff switch that is just true or
false there is a way to make used query only automatically run when the state is true and never run when it is false use Query makes this super simple as part of your query options here in addition to
having the query key and the query function I can specify another field that is called enabled which you can see the intellisense picks up on here and I can just pass in a Boolean for that when
this Boolean is true this query will automatically run whenever it is false it will not automatically run and what I mean by that is we can still get the query to run if we manually force it to
like in the event of calling the refetch function but it's not going to run every time react rerenders this component this is really helpful in scenarios where maybe you have multiple Ed queries in the same component and you only need
certain ones depending on some state in your component I'll show you a really neat use case of this closer to the end of the video one thing that you might notice when working with tan stack is sometimes your query options can get a little bit complex
for example if I look at all the possible options for the query options you can imagine that if I had a bunch of these on here this file could get quite a bit messy one thing that I like to do is make my query options in a separate
file not only for this issue but also to make my queries reusable across my entire app what I typically do is make a folder somewhere in my directory called something like query options and then I
would put all my query options in here if we're talking about just this query for this to-do that we've been hitting earlier we can make a file in this folder just for that query so I would normally name it something along the
lines of create too query options. TS
just like this this would just be a function called create Todo query options and then it would just return query options which is from tan stack what's nice about using this query
options utility is that you can get type safety and intellisense versus just using a straightup object for example if I try and pass an object here to get our options and I click control space for intelligence we have the intelligence of
it telling us everything that we could possibly pass into this so that's kind of just a useful thing to have so what I will actually do now is go back to where we have the query options already created and it's this object right here
I'm going to go ahead and take this and I'm just going to pass it in to our query options just like this and we have our get Tod do function here let's actually get rid of enabled let's say we don't want that in this case and for now
normally I would put this in a separate file this function but for right now we can just move it to here so it's happy now we have this create Todo query options function that can be called anywhere in our app so we don't have to
keep rewriting the query options if we're calling it in multiple places how we'd use it is super simple we' first want to make sure obviously this is exported let's just export default function create to do query options go
ahead and save this file and now in here instead of having to actually do all this with the query options we've already just created that all we need to do is call the function create to do
query options just like this call it like a function and it'll be happy and and voila it behaves the exact same as before in the exact same way you would expect it to however we just extract it
into a separate file so now we can use this wherever we want in any possible component and we've made it modular and reusable which is definitely nice to have and something I definitely recommend if you're in a medium to large
size app next I want to talk about something with typescript if you're using typescript and all that your query function is doing is simply using Fetch and then returning that as a parse Json
response you're going to notice one thing if I go into a using use Query and I hover over data notice how data is of type any having a data of type any means that you're not going to get any sort of
type safety when working with this data object so if in my jsx I try and do something like let's just replace this down here I just try and do something
like I don't know data. hello I'm not going to get any problem saying that this property doesn't exist even though it it generally does not exist but typescript doesn't know that because data is of type any in the the only way
it's going to eventually find out about that is if I save the file the page is going to crash because obviously we have an error it's going to say we can't read this property because it doesn't exist so now nothing's displaying on the page but typescript can't catch that if it
doesn't know what it's supposed to look like so if we go back to what it was before I'll show you a way that we can fix this one way that we can quickly fix this is to specify the return type inside of our query function here if we
know the structure of our endpoint response we can make a type for it so for example if we go over to this tab here and we look at the general structure of this data here we can see what it looks like we have a user ID
that's a number ID that's a number title is a string and completed is a Boolean so let's actually go down here and make a type for that so we would say type too
something like this user ID is going to be a number we would say ID is a number title is a string and then completed is
a Boolean and this could be the type that we would use for our return now on our query function definition we can specify that we want the return type to be of type Todo and it's going to be an
array of these objects so we can add square brackets but one last thing remember that we're when we're working with async functions that it's always going to be a promise that we're returning so let's wrap this in a
promise just like this so we are returning a promise of an array of to-do objects if we now save this file and then go back to our app and hover over
data notice this it's now telling us that it can be of type to-do or an array of type todos or or it can be undefined which is pretty standard what this implies now is that if I go into our jsx
down here and I try and access a property of data me get the first element in data and I try and access something and I could do any one of these properties and there would be no error on this because these are all
valid properties however if I try to do something like hello it's going to have an error now saying hello doesn't actually exist so this is one easy way that if you know the structure of the
response you can just type it manually in the function that way you can get type safety when working with data in a more complex app you might consider using something like Zod that will do runtime validation for your types right
now we're just telling the fetch function what the return should look like but if there's an actual mismatch between what we put in in this type and what the endpoint actually returns we're most likely going to end up with errors
but this is a topic for another video however let's go back to one thing real quick we're backing this page and we hover over data you see you have a red squiggly line here that says data is possibly undefined and this is actually
the default behavior of the used query hook if you're working with typescript this can be annoying sometimes because anytime you try to access properties of a return object or array you may get type errors saying it could be undefined
and that's absolutely valid because it could be undefined if you want to use a query where the data is guaranteed to resolve before it is returned to you and it can't be undefined there is another
hook in tan stack called use suspense query and it works very similarly to use Query except that it guarant anes that data will never be undefined and here I want to show you exactly what I was
talking about if I take this query right here and I go ahead and just copy and paste it up here and instead of using use suspense query I just use regular use Query now if I hover over these look
at this the data for the use Query is going to be of type to-do or undefined if I hover over the data for use suspense query it is only of type Todo not a union with undefined I'm getting
an error here because I have two things that are of the exact same name so I'll go ahead and delete this but case in point is that the used suspense query is great for when you're working with things you want to make sure data is
always defined and there's no undefined case what it's also great for and actually what its primary use is is if you're wanting to use the react suspense component so for example let's say I have a card component that is being
rendered in my app inside the card component I have a use suspense query that is fetching some data it is the same exact use suspense query from before I just moved it down a layer into this card component wherever I'm
rendering the card I can actually wrap this card here in a suspense just like this and this is from react so we put this between the opening and the closing tag of the suspense just like this I can
go ahead and give this suspense a fallback prop here and just pass in a loading spinner component now what this actually does in conjunction with us suspense query is if there are any
suspense queries inside of this suspense right here for example the one that we have within the card it will render the fallback component if the query is not yet resolved otherwise it's going to
render out the card this is a very common way loading state in react is handled and the use suspense query is great for this so now if I go ahead and save this file and I refresh the page notice how we have a loading spinner
until the query is resolved and then it displays the actual card so I'm not having to do like a turn area checking whether it's pending or not this is just a suspense that the US suspense query is really good for handling if you haven't
seen this pattern before well here it is and US suspense query is a great way to do it the one downfall of us suspense query that I found thus far is that unfortunately you can't use the enabled
property on you suspense query to mimic conditional querying so what I mean by that is if I were to take this whole thing make this an object do a spread on this and then add enable to the end of
this here and I tried and add some fake Boolean like this to mimic the conditional quering that we can do with Ed query it's going to say that I can't do enabled H year it doesn't exist on
these use suspense query options and that's because it actually doesn't the whole deal with use suspense query is that it guarantees the query will return something and adding a conditional that
triggers the query sort of breaks that behavior you technically can simulate the same behavior by passing in your own sort of enabled Boolean and then you know somewhere inside the query function
like inside here we could have you know something that would say if if this thing is true then return null otherwise go through the query or something like that the problem is if you're returning
like this now you've just made nla potential return type and at that point you might as well just use use Query so if conditional rendering is something you want to do use use Query and if you're trying to use use suspense query
just know that you can't do the enable tag and conditional querying like that one of the great things about tan stack is that you could have as many queries in the same component as you need sometimes especially in larger apps
having the ability to have multiple queries in the same component is necessary I'm going to split the section of the video into two parts part one being when you have multiple queries that are exclusive to each other and can
all be ran at the exact same time part two being where you have multiple queries but they need to run in a certain order because the results of one query might impact the options passed down into the request of another for the
first scenario where we have multiple queries that are all completely independent of each other there is a super easy utility function from tanack that allows us to execute all of our queries together without having to call
each one separately this function is use queries yep literally just use Query but plural rather than taking in a single query use Query should take in an array and here is where we can list whichever
queries we want to call use queries all we need to do is replace use Query with use queries make sure that is imported and rather than taking in a single thing here this actually is going to take in
an array of possible queries and actually this should still be an object here so there should just be an object with a property called queries and then this is where the array itself is passed in off screen I just created two more
queries so we now have have a total of three that we could possibly run now that I have multiple queries that I want to run I can go back to the used queries here and in this array I can add another one I have one called create user query
options and I also have one called create post query options just like this let's goe and call this like a function save it to get some formatting and here you can see we are calling all of these
queries right here and instead of this being destructured as data what we instead need is to destructure as an array cuz what it returns as an array since we're passing in as an array in
the first place and we could just do something like result one result two result three like this if we wanted to where it corresponds at the correct order so this is the result for this first one this is the result for the
second one this is the result for the third one that being in this order or we could also do we did before where we just destructure data out of these and if we save it you can see It'll behave the exact same way this use queries hook
provides an easy way to consolidate all of our queries into one function now there's nothing in inherently wrong with using multiple used query hooks but if you wanted to do it this way this would be an option what's awesome about used
queries is that it doesn't just apply to the used query hook there is also used suspense queries just like this that behaves in the exact same way as used
queries but it acts as a suspense query instead and this method of doing use queries or use suspense queries is great if you have all the queries in the same file that are exclusive of each other
but if the parameters of one query rely on the results of another another this does not work for example let's say I have a scenario where I have two queries in a single component the first query
retrieves all the users from an endpoint and the second query takes a ID that is a random ID from the users and then plugs it into this create post query options we're getting a post based on a
random user ID obviously these queries can't run at the exact same time because the second query right here is dependent on this ID which is dependent on the first query so they can't run at the
same time time they have to run one after the other I showed you the enabled Boolean earlier and that's exactly how we can actually get around this in the second query I can take this here put
this in an object and just spread it so we can add some properties here scroll to the end here and I can just add an enabled Boolean and we will just check to make sure that this users is not
undefined essentially so we're going to say if users and we can turn this into a Boolean by adding double exclamation point so essentially if users is is defined we're going to enable this query
if user is not defined this query is not going to automatically run alternatively if we didn't want to do it this way there actually is another way to do it if we made this first query a suspense
query that is going to guarantee that users is going to be defined here in which case we wouldn't have to put the enable tag on this because it's kind of redundant in the sense that this is
checking for if it's undefined or not but we already know because we're doing a suspense that it will be so we could just get rid of this like that do it as a suspense query and it should behave in
the exact same way and with that we're going to end this video here I've covered some of the main talking points of tanack but there's so much to this library that I'll certainly be covering more tan stack topics in future videos I
hope you are able to learn something from this video and if you don't already use it I'd highly encourage you to use tanack in your react projects as it helps tremendously and is a legitimate professional way of handling querying
take care everyone and I'll see you in the next one
Loading video analysis...