TanStack Query v4
with Dominik Dorfmeister
State management is hard. TanStack Query aims to take the pain out of data fetching, caching, parallelization, and much more. Maintainer Dominik Dorfmeister will teach us all about it.
Resources & Links
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: Hello, everyone and welcome to another episode of Learn With Jason. Today we have Dominik, how are you doing?
DOMINIK: Hi, Jason. Glad to be here.
JASON: This is going to be a great episode. I am a big fan of the stuff you are working on but before we talk about what you work on let's talk a little bit about you. For folks who aren't familiar, can you give us a bit of a background on yourself?
DOMINIK: Hi, my name is Dominik. I am a web developer. I live in Vienna in Austria. If you know me is because I write a blog on react and TypeScript and more likely because I happen to maintain the quite popular library TanStack Query that we are here to talk about today.
JASON: Awesome. So let's talk a little bit about TanStack in general. TanStack is I believe named after tanner lensly who has been on the show before. It is a suite of tools and one is a query which you are maintaining. I know the idea when it first came out was they were a bunch of React tools but it sounds like that is no longer the case. These are usable across virtually any framework, is that correct?
DOMINIK: Exactly. The query with version 3 had an agnostic core, a directory where the core logic lived that wasn't tied to React. We haven't published this. It was just internally. But people started to pick up on this and create community libraries based on React queries but with other frameworks. There was Vue query and Svelte query and then more libraries like React table and a new version came out built on the same concept. We can have an agnostic core where all of the logic lives and we will have adapters for React and for all the other popular frameworks or even you can just use it as it is maybe with j query even. You can use it with anything. All the other libraries of the TanStack followed. We made a mono rep for every package and released multiple packages with every name change under the name TanStack. The library was released as version 4 as TanStack Query. You can still call it React query but it is also the agnostic part in it where other frameworks can profit as well.
JASON: Awesome. Excellent. Excellent. Well, very cool. So I have had Tanner on the show talking about query when it was still called React Query but a lot has happened between V2 and there is also a lot more possible with query today. It sounds like you have made a huge raft of improvements so maybe we should talk for a little bit about before we get into what's different from last time and what's possible, before, what is the overarching purpose of TanStack Query? Because the -- my understanding when I first heard about it is TanStack Query is a way to fetch data and I am almost positive that's an incorrect understanding of what it is for.
DOMINIK: It is a very popular description of it. I have always heard a lot that it is the missing data fetching solution for React. And this is one thing that it does, yes, but it also is kind of not the right way to describe it maybe. I have seen lots of questions on Twitter lately about should I use React query or fetch or react query and what should I fetch my data with. The surprise about this is TanStack Query is not a data fetching library. It doesn't do any data fetching for you. The way I would describe it is I would probably go for a state manager. Data fetching itself I think is not the problem that we are trying to solve. It is just that data fetching itself libraries exist like Axios and Fetch is an easy in-build way to fetch data but what you do with the data in the application is where the problems and complexity come in. So you want the data to be available in your application whichever framework you using. You want your components to render correctly with the data. You might also want to update the data from time to time. There is a lot of state management going on that needs to be handled around that data. TanStack Query does this very well for anything that can produce a promise. When you create a query, you are providing it with a function that returns a promise and of course, if you fetch data this can be done with Axios and Fetch and GraphQL request to fetch from GraphQL end points and you get back a promise and that's what TanStack Query uses to manage that data for you, cache it, keep it up-to-date with smart heuristics. We are trying to keep the data that you are seeing on the screen up-to-date with what's happening on your server but you don't have to use it for data fetching if you don't want to. There is other ways to produce promises. You could use it for reading the status of a device like a camera or something that's also giving you back a promise. You can use it to read from index DB. There are lots of things you can do that doesn't need a network connection. Yeah, that's also aging states but most of the time it has to do with data fetching. That's what people mostly use it for and that's totally fine but one of the improvements we made in version 4 is that we have kind of taken away all the assumptions about you need to have a network connection to make a request because we are a library that makes requests. We have new options for network modes so you can opt out of the behavior and say I am using TanStack Query for managing aging state that doesn't come from the network and it will work just fine.
JASON: I guess this starts to bring up a whole different question which is if I am a React developer who has been doing this for a long time, I think I wrote my first React in 2016 or something, and throughout the years I have reached for different tools. I started out using redux way back in the day as sort of where I focused on state management. And then when hooks and context came out I made a pretty heavy switch to React context. I have, you know, I have dabbled in other state management. I have used state machines. Looking at that landscape of like how state management is done in apps today, does TanStack Query augment those tools? Does it replace those tools? Like what would you reach for in our apps if you are planning on using TanStack Query today? That's a pretty open-ended questions but --
DOMINIK: It is a very good question because that's something that's asked a lot. Can I replace Redox with this and I know Tan has a great video on this by the way. We have to distinguish between kinds of state. I think when we were using redux there wasn't anything else and all our state was put in redux. It is like the state in the UI or the state that comes from the network and we put it into a global store and distributed and and that was the best we could do. Now we have more tools available and we have to really think about what kind of state do we have and how do I want to manage it. I think aging chronic state has different needs than synchronous and UI state and that's why I wouldn't try to use one tool that fits them all. This is also a question I get a lot. Can I use TanStack Query to manage the state of my dialogues that are open or, you know, different other states and why you technically can or could do that, it is not a good idea. I think there are better tools that do this very well if you need to. Let's say what React has built in, state and context, that also one way to do but that's more state distribution than state management. I would use TanStack Query as everything that's aging chronos and at work we have our own design system. Is the dialogue open? Am I seeing this on managed tools? That's happed -- handled intarnally. A lot of things can also go into the UI. You can deicize -- decide if you want to use redux or anything else. All in all, it goes together and I think it is a lot better we have started to separate the States by the need that they have to be managed rather than is it global or is it not global which was what we initially did.
JASON: Gotcha. To repeat it back to make sure I understand it, you are saying asynchronous state is ideal for TanStack Query and synchronous state you would want to reach for something like zeus stand or state machines or something like that?
JASON: I am going to ask the very beginner question to differentiate. How do you know if state is asynchronous or synchronous because there is a couple of things that happen. I know asynchronous in the format of like I am going to make a request to a third party API and get some data back and, you know, that data is tied to my user account and all that kind of stuff. That is obviously asynchronous. Then there is stuff that's obviously synchronous like I have clicked a button to open a modal and then I am going to click the X button to close that modal. That's synchronous state. But it feels like there is a bit of a blurring in the middle because you just mentioned for example the URL as a state manager. That I am assuming is using the query string so for like filtering and stuff like that. Is that synchronous or asynchronous state because there filtering is going to affect my asynchronous data I requested, right?
DOMINIK: It would. And that is where you get into combining the two things. The answer for me is this is still synchronous state because you have a filter form and apply the filtering and then you write it somewhere, could be the URL, which is often a good place to store this, could be just some local state, could be some global state manager and then you would want to trigger a query depending on that. That's where you combine the two but in itself React Query or TanStack Query wouldn't really manage those filters for you or those inputs for the query. You would do that because it is still synchronous and you can apply the filters synchronously and then I am saying it drives the query. You have to make sure these filters are respected in the query.
JASON: So then to repeat that to make sure it is sinking into my brain properly, when we talk about synchronous state we are talking about the user performs an action that should modify what's available in the UI. So if I am opening and closing a modal that's an obvious one. If I am clicking buttons in my filtering on my form that's me taking an action and I expect an instantous do I the thing and the thing happens with no delay but in the become ground we might commit that filter and I might save that search I have done and do an asynchronous request to get a limited subset of data that's now like the result of the synchronous changes that I have made in my filtering. I would manage it not like in two places but I would manage the filter set synchronously through the URL for example and then I would update the asynchronous data which is the data tied to that filter state. And I will asynchronously load that in TanStack Query to -- if I were to say yes, finalize that search and I want that to be my subset of data and I would commit that to TanStack Query.
DOMINIK: Exactly, yes. This is a good place to actually start to look at that because this is something I really wanted to show today because it works a bit differently from the mindset that people usually have when you do data fetching or when you implement something like a search or filter form. Usually you have a button and apply the filter and say I click the button and now I want to fetch the query. How do I do that with TanStack Query? There is a refetch function that you can do it but you cannot pass parameters to it. Why is that? This doesn't fit my mental model. With TanStack Query actually go a little bit of a different route and yeah, I think we can just take a look at it if you want.
JASON: Let's do that. Let me swiv us over into the pair programming view. I am going to move this off screen show it doesn't give anybody the infinite falling feeling. Before we start talking about code, let me do a shout-out. We have Maggie from White Coat Captioning doing live captioningt today. You can click the CC icon in the video player and that is made possible through the sponsors, Netlify, NX, New Relic, all kicking in to make this show more accessible to people which I appreciate. We are talking to Dominik who is on Twitter at Tkdodo. Everybody go and give Dominik a follow. We are talking about the TanStack Query. Go ahead and check that out for context. I think I saw Tanner show up in the chat. What's up, Tanner? We have had Tanner on had show before talking about React query. If you want to see this, I think this was V2 we were looking at, so you can find some details from tanner as well on there. OK. That is as much as I know. What should I do first if I want to start digging in.
DOMINIK: Let me add, the show from Tanner is still perfect to watch. Even though we have version 4 and are starting to work on a version 5 to me TanStack Query is more of a concept you need to grasp and get and then the rest of the changes that we do are kind of like just syntactical or a little bit of changing semantics here and there. If you are using TypeScript you won't have much problems updating. Everything Tanner is saying in that video is still absolutely gold.
JASON: Thank you, Csharp Fritz and friends for the raid. We are talking about TanStack Query today. We are going to use use it for state management and show that it is more than like a competitor to Axios or Fetch. It is a full blown state management library. With that, I am ready to write some code. What should I do first?
DOMINIK: I have prepared a little repository. Do you have the link? Should I send it to you again?
JASON: I don't know that I have it on hand. Sorry.
DOMINIK: I can drop it in the chat.
JASON: I am going to share it again so it shows up in the show notes and all right. I have got this. Let me go and -- here I am going to make a folder. I am going to open this up and we can see in here -- now I have got this here. It looks like what a PMPM site. Should I do the install?
DOMINIK: And you can do PMPM dev and we can also go through the code first.
JASON: I will follow your lead. Want to look at it and then we will look at code?
DOMINIK: We can look at the code quickly. There isn't much yet. It starts in main or ep.
JASON: Here is main. It is pulling in a Veit-like way. We have the root here. The actual provider. Then we are passing in a client so this having used basically any kind of data management and state management this feels familiar. I have my client to make the calls, the provider to make sure it is available everywhere in my app, and then the DevTools, this is kind of cool and I assume we will dig into this later on.
DOMINIK: Yes, we can. Once we see them if you haven't worked with TanStack Query it comes with a DevTools panel that you can expand in your browser that will show you exactly what is in your cache, what's the key, what's the value and how is it setup and you can interact with it by maybe triggering a refetch to see how it behaves and stuff like that. It is helpful to analyze and see what is in your cache. It looks like that's using a hook to load up some product data and give us a search result so we are showing status.
JASON: We got the result itself and a no products. Then we can go into -- I want to look at this hook.
DOMINIK: This is something I always encourage people to do. Even if we are just returning use query. It is mostly advised to create a custom hook so you can change the internal however you want. You can also use this hook in all places in the app or the places where you want to and need access to the product and that's where the state management part comes in. You are kind of abstracting the query key and query function away in a custom hook and then everywhere in the app you can call use product and you can display them wherever, it doesn't matter where in the app as long as it is under the query client provider, you will get the cache state and define on a fine granular level how long you want the data to be cached, when you want refetches and things like that.
JASON: OK. So looking at this, we have a couple things happening that are like specific to use query. First and foremost we are calling used query which comes out of query and that gives us -- we pas two options in, a query key and a query function. The query key if I am understanding is what you using to identify that this particular query is like unique? When I make a call to the products I am telling query this is my products' query and lots of things can modify it and if they do anything using this particular query needs to be updated. Is that correct?
DOMINIK: The key identifies the place in the cache where the data lives. Anything product related the product list lives on the product. I have a couple articles out on how to manage those query keys efficiently because once the app grows writing products in an array and maybe multiple places probably doesn't scale well. I have had a couple issues where I copy/pasted the key and didn't realize I was using the same key in two places I shouldn't. So I encourage you to check out the blog post about managing query keys. It is one of the thing do is get right with TanStack Query.
JASON: I am going to put that up on the screen here. Looks like Moshifon shared it but I will share it again so it shows up in the show notes because I have a robot. This is good pre-reading if you want to give yourself a leg up here. The second piece is the query function and the query function is how we load data into query, right? So I can put effectively anything I want in here. I could just return a hard coded array and that would put it in. It doesn't do me a lot of good but that's like the hello, world case. Return my array of 1, 2, 3.
DOMINIK: It is perfect if you start with that or do that. I get a lot of people that say I cannot show you a reproduction with TanStack Query because my API is private. You don't need to call an API. You can just go into the query function and say return here is my JSON object or here is my array of things. I have made it, here we are using the angolia search API and it is a function that returns a promise of data. Doesn't really matter what the search does under the hood. But I have made it a bit slower with a sleeve because it is so fast otherwise you won't see what is going on. That's just a little thing that makes us wait a bit longer.
JASON: This gives network lag so we can see the fetch, the loading statuss and stuff, right?
DOMINIK: Yeah. This might actually be good to build in the DevTools and make it slower for us to see because I do this a lot.
JASON: There is a question in the chat from the veepen mistra, sorry if I mispronounced your name. In V4 do we need the keys of query function key and then moving to an object with properties instead of arrays of keys, I think this is something you talked about is where you are headed in V 5. Do you want to talk a little about that?
JASON: This is future proof. If you use this then V5 you will just install and good to go.
DOMINIK: Exactly. You can use the syntax today. It is a breaking change because we will remove the other syntax. We have a lint plugin that will tell you to use this syntax and auto fish it. I have this turned on. -- auto fix. If you have use lint enabled and were trying to write use query with key and function I think it should. I haven't tried this but let's be a bit spontaneous and see if this works.
JASON: I could do something like a key.
DOMINIK: And then like a function.
JASON: And it says...
DOMINIK: Object syntax is preferred and if you autofix that it should kind of --
JASON: Did I click the right thing? I don't use the quick fixes very often. I quick fix --
DOMINIK: It fixed the first one. It doesn't work? It shows the error. It worked in my editor.
JASON: There it did. I don't know why it wasn't working when I was clicking but when I used the shortcut of command period it did work. That fixed it for me. That's cool. That's really cool.
DOMINIK: We are also thinking about very likely going to do a code mod that will do this for you as well. We have done this with version 4. The breaking changes were delivered also with a code mod that you can run over your code to make it easier to do this. Also for this one, I hope that the pain is not that much. We have also rewritten all the docs. I am at the moment rewriting my whole blog to use this syntax everywhere. It takes a bit of time. It is a good change. From what I have heard it will also make solid query be a lot better because of the fine grained reactivity you can just have one object instead of --
DOMINIK: I don't know too much about solid but it is in the roadmap that we have already shared that it will make solid query better to use.
JASON: This is the road map. There is a link in the chat. Prodgy 7KX says they be making my life too easy. Correct. You don't want to have to think too hard when you do this stuff because you want to use all your brain power for the feature and not managing the data and state so you can build the feature, right?
JASON: Cool. We have got -- this feels like the product list. We send a filter or query to Algoria to a list of products. We have an is-loading status. The sleep forces that to be true for half a second. We see a loading status and then we see the list of products and this is what comes out of query. Here is my link. All right. Here is our product list. This is the tools. The DevTools.
DOMINIK: Exactly. I have started them with initially open. Usually they start up like this with this flower symbol at the bottom. You see one cache entry. This is the product's cache entry. You can see what is in there and what's the state and all of the internal in there and makes it easy to debug. You have options at the top where you can see that, you can refetch it, you can invalidate it. If you click refetch it will probably just go into fetching state and you will see that it is fetching an animation on the screen if you click that button it just refetches.
JASON: Extremely cool that that is just doing the thing. OK. This is already, I feel, kind of revulatory for me. I would have a pre-tag at the bottom where I dump state like JSON stringify. Coming in and click had helper buttons and test on the fly instead of refreshing the page or write code to throw an error -- that kind of stuff is just show nice. Yes, exactly. I am definitely still going to do it in other projects but it looks like I won't need to if I am using Query.
DOMINIK: Just use the DevTools. They will always so -- show the single source of truth of what is going on.
JASON: I am going to close this. Let's look at the outcome here. We have our status. I am going to reload the page so we can see the 500 second load. Then we get the list of products which had looks like you hit the best buy API. We have best buy data here. What do you want to do next?
DOMINIK: I would like to show this is now a list of products but it just shows like the top 10 products. What I would like to show is how we can add search capabilities to this product list. What would you need to do if you want search field and type into it and hit enter or click the apply button and then you want to perform the filtering and have this filter then be applied on your query because this is one of the things where you need a little bit of a different mindset because we are not going to actually do any data fetching when we submit the form? We are going to come to the prepared component called search. You can just, yeah, it is a very simple uncontrolled form where you have an input field and there is on-submit which is going to call the on-search prop where we are going to give the value to the parent component. This could obviously be much more complicated. You could have lots of filters or sorting options and you can use form libraries to manage the state internally here. I have a blog post on how to use it with React hook form. For management, like we said this is also synchronous state and client state, because usually when you reload the page that state is gone and this is where the boundaries are drawn. Let's add this search component in the above all product list. Let's do that. If we go to the --
JASON: Going into my product list. Let me make sure this stays open. I am going to go into the product list. Oh, go to the app.
DOMINIK: Above the product list let's put the search component.
JASON: Then I need add some props here.
DOMINIK: I made them mandatory. On search and default value.
JASON: And we need it to take something so that's one of these?
DOMINIK: Yup, TypeScript.
JASON: By default it is going to do nothing but it is just going to show up as an empty search and because it is uncontrolled we can throw in whatever we want. If I want to search for Apple products I can do that. Because we used a little UI tip. Use the right component type. See how it is a type of search? And thec check it out. That UI is built in and now you don't have to write custom code. You can just use the dang platform and I love it.
DOMINIK: I forgot to add a label so if you do inputs always add the label. That's my bad.
JASON: We can do that right now. Watch. Here we go. Label HTML4 search and then we will say search and add a quick ID of search.
DOMINIK: Thank you. That's perfect.
JASON: Now we have a labeled input and I am just going to -- it doesn't need to be beautiful.
DOMINIK: It just needs to be there.
JASON: Just needs to be there.
DOMINIK: We typed something in and pressed enter and we want the query to re-fetch. This is where, like I said, if you are having some -- have used maybe something else before, you would probably want to call like the API again on the search button. This is where React Query differs a little bit, right?
JASON: Yeah, or I am thinking of something like an auto complete where you are calling it on every key stroke you are calling out to another API and the search is another API call. It is a lot of HTTP requests when you implement a search form in the ways I have done it.
DOMINIK: The key think about React Query, the key thing, is that all of the dependencies that you are using for your query have to go into the query key. That's why it is a key. Once you do that, there is nothing more you need to do to actually fetch data because what React Query does is it looks at the key and when the key changes, it is like a -- I don't want to compare it to user effect but like a dependency array. Once it changes, it knows it needs to rerun the query function and create a new cache entry for that new key because if you use something that uses a key that doesn't have any data yet, we have to go and fetch it otherwise it won't be shown. The only thing that you need to do is to take that search input in the on search and you are going to put it into a client state manager of your choice. For search, this is usually you can put it into a store or the URL, here for this example, we can create a simple use state at the very top of the app. Here I would do a use state and set filter and whatever you want to to call it.
DOMINIK: On search, we are going to call set filter. When on search is being called with a string we can call set filter. On search equals set filter. The string let me put into the state the component rerenders. Of course, as I said, it doesn't have to be use state. It could be something globally available. Now the only thing we need to do is here we can just do what's most simple and also straightforward and we prop it through. We have to pass it through the product list and pass the filter. Yup. And then in the list we have to actually accept the prop.
DOMINIK: Same thing for user product.
JASON: Did everybody see me write TypeScript? I am learning. I am getting better.
JASON: Go ahead. Sorry. That was me going off on my tangent there.
DOMINIK: Now we need to pass the filter again to use product because the hook actually needs to consume the filter as well.
JASON: So we just pass it in just like this? Whoa. This is going great. I am going to go back to use product and use product is going to take the filter which is a string and then we want that to be -- like do I also put it in here?
DOMINIK: Eventually, yes, but just to show off don't do it right now. You would pass it into the search fangs as -- function as a query. Right here you would just pass in the filter instead.
JASON: It is setup in such a way that any kind of regular string it will automatically do this? I was getting ready to write a filter so this is -- already I am happy. It is mad at me, see?
DOMINIK: Yes, it is. I wanted to point was this is the second ES lint rule is the exhaustive dev lint rule. It tells you if you are using something in your query function that is not part of your query key and you should put it into the query key and it is auto suggestable with a fixed rate and you can add filter at the end but you can do it manually. This is something very new.
JASON: It will just do it for me? Let's do that.
DOMINIK: I think it just adds it at the end. This is really the power of Open Source. I just asked about this on Twitter can someone write an ESlint rule that auto fixes the missing dependencies and it happened. Super happy about it.
JASON: Holy crap. Look at that. This is incredible because I have like -- I know how much work this is. I have written this from scratch where I am sending off the query and you get the list of results and you have a filter and thren you don't have 10 results and you have somewhere between 0-10 results. Now you have to query again to make sure you have the right number or are sending off a new http request for the filter query and there are so many little fidly things you have to do when you are writing remote filtered queries and they all just disappeared here. I mean, part of it is we are using Algolia which makes it easy but how much is the fact I don't ever want to deal with like managing the query state and stuff. This is very -- and look, each one of these now is filtered for us. Like this is really cool stuff.
DOMINIK: This is one of the things I really wanted to show because you added to the query key you get a separate cache entry with ever filter. That has a bunch of implications mostly positive implications. One other thing is all this data is cacheed separately and if you were to search for Apple again you would see you would instantly get the data for Apple. It would do a background refetch because you have used the key again and the query data is considered like outdated because we haven't defined it any differently. The default value of the state time is basically zero so you would always refetch when you do something. You don't see the loading spin up. If you filter for something you haven't searched for, I think you should see a loading, this hard loading spinner, loading state first.
JASON: I think I saw -- yeah.
DOMINIK: Then you see a quick splash of loading spinner because for this entry we don't have data yet but all the other entries are still cacheed. If they are not used they will live in the cache for another 15 minutes and you can customize that. As long as you are using the app, all these queries will be here and the data is still stored, right? This is also why, because we have a separate key, a new query key, that's the reason why the automatic fetching works and you type and set the filter. It will try to create a new query key, doesn't have it yet, so it goes and fetches it. That's why you don't have to fetch datamanually. You define the dependencies of your query and then you manage the local state that has these dependencies. In this case it is the search input and the filter value and whenever the filter val to a changes, this could be various things. You could have a button that sets the filter and then you don't have to like filter for, I don't know, some single word or something that is a button. You would not need to implement the on-click of the button that does fetching again and then you have five different places where you do fetching. You have five different places that manage that filter state because you have the complex filter but the query just sees the input that it needs to run.
JASON: This is extremely cool. OK. Weird. We are cruising here. We have gotten so much done in so little time. What else should we look at while we are in here?
DOMINIK: There is one more thing I would like to get rid of because I really don't like loading states. Like the ones that show a spin. We have seen if you search for banana or something, I search for Apple and then banana, you would see every time you search for something new you will see this hard loading stage. There are better ways to handle this or there are ways to handle this in a more gracious way let's say. One of them is a feature that we have that's called keep previous data. What that does is tanner said we implemented this because he wanted suspend before supend is ready which does a similar thing. You can use this feature to say I want to keep the data of theprieve previous search available on the screen while I am going to fetch new data and all you need to do in this case is pass in keep previous data. We are probably going to change the API a little bit. I am personally not a fan of all the booleans that we have. For this one we can probably find a better API. When you do keep previous data, you can search for something that doesn't exist.
JASON: Sorry. That was a different one.
DOMINIK: You can just start to maybe reload the page and then everything is gone.
JASON: Good call.
DOMINIK: Then start from scratch and search for whatever you want. This green stays in tact. And you can see next to the status we see a little fetching indicator. There are indicators that tell us we are fetching but we are not really showing a hard loading state. There is nothing that really changed for the user except that they were basically still on the same page but are just filtering the data a little bit differently. There is one more thing we can do to maybe make this UX wise depends on what you want to do. I still would like to have an indicator that this data is maybe outdated or stale. For like this, the text in the search box hasn't applied so it doesn't correspond to the data that you see on the screen. What we can do is go to the list component and now that we have keep previous dataset, what we can look at it is a return value that is called is previous data. That's another state that will return from use query and tell us the data you are currently seeing is actually not the data that your key is currently looking at because I don't have it yet but this is the old data that we are looking at, right?
Gotcha. We would get --
DOMINIK: We could give it a gray color or set opacity.
JASON: Let's go with that. If it is previous data it will just fade out a little bit. OK. Then we can go -- we have already. I am going to reload so I don't have to come up with new search terms here. Not enough fading. How about 5%? Make sure it is really -- yeah. This is great.
DOMINIK: Now you have a skeleton of sorts with the data previously there. You have a transition effect you are going from one search to the other and of course UI wise you can do whatever you want here. We are not getting into the design here but the speed with which we can mong up and hand this to the UX team to think through what's the right way to indicate this to somebody visually and what's the right experience for somebody. We as developers can provide them with the raw tools and then they can come become and say tweak it like this and that.
DOMINIK: Exactly, yeah. That's pretty much what you can do with it. If you are not searching for something that already exist, you can see it goes into this gray state. You can see you get the full Apple data back immediately. That's the stale data and not the previous data because we have it already. You have a differentiation now between for something I have searched for already we are getting stale data, for something that we have fetched not yet, we would just show this. Actually, let's do one thing. Let's set because we can also show how to just read from the cache and not fetch every time. Let's probably go to the query and give it a little stale time. That's something I want to quickly show.
JASON: Go to the query.
DOMINIK: One question I get is why is write query fetching so often. I see so many network requests like every time I click on the window. I want to turn off all the smart heuristics that write query has because I don't want to fetch every time if where have searched for it before I have data if the cache. Especially when you are coming from a world where you have fetched on mounds and never again. This is what you did with redux, you fetch one array, ignore the linter, I want to fetch once, I have my data and I am never refetching again. Write query is more optimized for keeping your things up-to-date so it errs on the side of fetching too often rather than not fetching often enough but there is a very convenient way to just customize that and that is the stale time setting. All the hir heuristics that React Query have only come in with the data is considered stale but per default that state time is zero. Every time you focus the window or change the key we are going to give you the stale data and refetch in the background. If you don't want that and say where fetch something and this is good for the next five minutes and I never want to fetch again in the next five minutes you set the stale time it to a number in milliseconds.
JASON: If I can hit my keys properly... one of these. That's 5 minutes if I am doing my math properly.
DOMINIK: I guess, yeah.
JASON: I mean this is I am sure horrifying.
DOMINIK: No, it's fine. Let's just try it out.
JASON: I am going to search for Apple and get the loading and do the Sony and then we are going to do the Apple and so what we should see is that this doesn't fetch, right?
JASON: And it didn't.
DOMINIK: It just reads it from the cache. You might have noticed the color in the DevTools have changed. It used to be the yellow which means stale but now it is green and green means fresh. As long as the data is fresh, it will never refetch because the data is considered good. If you want that data, you just read it from the cache unless you invalidate it manually. If you click invalidate it is invalidated and it will turn yellow.
JASON: I invalidate and it immediately grabs it which is cool.
DOMINIK: You can go to Apple and invalidate the Apple which is not used at the moment. This won't refetch the Apple. You can just see the number on the left turned black. The Apple key is now invalidated. Now it will refetch when you use it the next time. It will not refetch immediately because it is not used at the moment. We are just trying to optimize here. If you invalidate all your queries, it will only refetch immediately what is used on the screen and everything else will just be marked as stale and it will be refetched in the background the next time you use it. It is kind of a really smart way to think about it, to optimize the difference between I need that data available in a fresh way right now or, you know, I don't use it right now, just keep in mind that the next time I use it, I want to have it in a fresh state.
JASON: In the chat someone is asking about slow calls. Like everything taking two seconds. Let's simulate it. I am going to refresh. I think it handles it and is on you to decide what the UX is if it is slow.
DOMINIK: We don't have anything like slow indicators. I know there are some APIs that say like OK, if the fetch takes longer than two seconds or five seconds, we will give you back a new indicator, hey, this takes too long and you can display something on top of it. It is something we can think about adding but it executes the query function and give use back the fetching state and you can think about what you are going to do with that state.
And you could roll your own thing in here if you were looking at, if fetching for longer than 500 milliseconds or something you could make a different call and change your UI with a little set interval or something?
DOMINIK: Or CSS for example, I think, you can do something.
JASON: Cool. All right. This is great. I mean we have got -- I am going to turn off that super long sleep there so we are not -- where was it? Back to a half second sleep. Is there anything else you wanted to show? We are cruising. We have like 30 minutes left on the clock.
DOMINIK: I can make your life harder with searching by persisting the cache to local storage. We can do that. It isn't new in version 4. We have had experimental persisters for a lot of time in version 3 but they were marked as experimental and we made them stable in version 4. What persisters are is basically a way to take your query cache, the thing you are seeing in the DevTools, and persist it to external storage which is, for example, local storage, or you can also use an aging storage if you want, React Native has an aging storage which is often used. What we need to do for this is in version 4 everything is a separate package, I think I if stalled all packages if you can look at our package JSON. We have something that is called the TanStack Query storage persister and the query persist client. There are two versions. Async and async one is available. If you want local search we can use sync and persist client is an another adapter that gives you a client provider that as a drop in replacement for there one we have now. If you use that, it will wire up everything that you need. You just tell it what's the storage you want to write towards and then every time you reload the browser window, your data will be loaded from local storage and put in there.
JASON: Cool. What the effect of that is where I am getting a search for Apple and set stale time to 5 minutes so if I search for Sony and then Apple again it is instant and not refetching. If we put it in local storage that means for that next 5 minutes, it will pull from local storage and not refetch at all so I can drastically reduce the amount of http traffic happening which if I am building something for low data phones or people who are going to be loading this mobile in rural areas I can make it so they only need to load data infrequently when something is new and almost never when it is something they already queried once before.
DOMINIK: As long as your app is open, you have the memory cache any ways, but once you reload the page or you kind of come back maybe tomorrow or in five hours or whatever, then your cache is gone and you start from scratch. You can say I don't want user data that is sensitive to be stored on local storage but the product list might be the same. I just want to have that immediately available. Then you can persist to local storage or kneother external storage and rehydrate it from there.
JASON: Let's give it a shot.
DOMINIK: I think in main is where the query client provider. Let's remove the query client provider and let's import the persist query client provider from the -- and the was the -- what was it called? Persist query client provider.
JASON: I got this wrong. I still need the client. Got myself turned around here. We will replace this one.
DOMINIK: It is telling you we are missing the persist options. We need to provide the persist option. We need to create the persist query client and tell it where it should persist to. For this, on the client, I would Crete a create a new constant and we are instantating the persister we want. This goes with create same storage that you need to import from the query sync storage persister package. The only required one is the storage. Storage and then this would be window dot local storage. You have to pass in the storage option. You can write your own storage so the storage needs to be confirmed to an interface and has to have a get item and set item and I think a query. All those are compatible with our persisters. Now you would pass those and this is a persister, and then you would pass this in as a persister to the persist option. And that's it. I think that's all you need. I can go use it now? Sure. That's it.
JASON: This is going to go down as the smoothest episode I have ever recorded.
DOMINIK: Hard refresh the page. I don't know if you have reloaded the page. Command shift R to hard refresh. Because we said the stale time is 5 minutes, the data is not trying to requery these. That's why I said I am making it difficult. You have a hard time getting rid of it. This is for this demo. You can open the dev query tools and move them to the right side. On the top there is a drop-down that says bottom and you can move it to the right. And you can see here is the query happening.
DOMINIK: And maybe filter it down to fetch request, I guess?
JASON: There is our request. When I run another one, it is going to be for Apple. There is our other one. Do one for Sony. Now I will refresh the page. Now let's search for something new. There is our query. Press the page again. Search for frozen.
DOMINIK: If we were to remove the stale time we set, it would load it from local stooge storage and show you immediately and also fetch in the back grund. If the data is still stale, we can give it to you. We can never remove data in the cache once. We also prefer this data over no data. Even if it comes from local storage and might have been there for 12 hours, we just give it to you and if it is stale is thats fine. You can display it. That's also an option. Usually it is better to display stale data than no data. It will make the background refetch for you depending on the freshness of the data or not.
JASON: It is not requiring me to really engage the algorithmic part of the brain I have to use for caching. I have dependencies on dependencies. There is are so many things that go horribly wrong when you start matching this up manually. To have it just happen so that I am -- what I am concerned about and I want to build the feature and the thing unique in the app, I absolutely do not want to write my own state and cache management. Those things are required for my feature to work but they are not my feature. It is the foundation of my feature. What I love about this is this was seamless to setup so I have not yet had to engage the part of the brain that is like how do I hook that up to this data. I want to use local data and make sure people only query when it is stale which I decided is everything 15 minutes. I am going to set a stale dataime and use this local storage and someone only sends one request per query per 15 minutes. That's incredible.
DOMINIK: It is. I also think so. Caching is hard. When people say I don't want a third party library just for data fetching, I think I get that initially but the data fetching is not the hard part. The hard part is everything coming around it. If you start to roll your own thing, you will quickly realize this is if you want to get it with good experience for developers and users, this is harder than you think. That's where, you know, TanStack Query comes in and takes the burden aframe you. I think does a very good job at handling those simpleuations. -- situations.
JASON: Questions about did I notice the on window focus? I did. If I come off here, and go back, it will just go ahead and grab new data for me.
JASON: This is something that happens to developers in development mode often. If you have the network tab focused and focus the app again it also triggers a window focus refetch. This kind of throws people off. They go to the network tab and clear it by pushing the X button and then they go to the app and oh, there is another request and go become and clear that request and click again. It is like why is this library always fetching from me? This is out of control. This isn't what useers doment they will stay on the page, hop over to Twitter, come back and do work and that's where you want updated data. If you don't want that in development mode you can set higher stale time or turn the flag off for development mode just to not spin server logs or something. I don't think it is a good idea to turn it off because it annoys you in development mode. It is like one of the best and I always know when a page has that or it doesn't, like, when I have a certain ticket management system and tab open for a long time and I come back and never trust this is up-to-date.
JASON: Got it. Very cool stuff going on. I am not going to lie. This is pretty incredible stuff. This is incredible to see happening with so little code. Really blown away with how much that did for us. We maybe have written 20 lines of code? If that. This is 15 lines of code and actually using it is another 2 lines of code when we get into the loading states and stuff. We have really robust state management happening in the app that has a lot of granularity. It just feels good to use. One of my heuristics for whether or not software is going to get a lot of pickup for me is whether or not when I use it, my gut instinct is like that was delightful and I feel like this was delightful. It never happed into the part of my brain I don't like using unless it is a problem for me. I like brain teasers when I am doing a brain teaser. I don't want to do them whether I am trying to solve a feature for work. -- when I am.
DOMINIK: Yup. And having to do this for every app and every time and every end point kind of gets annoying. And I likely get it wrong if I don't use react query.
JASON: Yeah, and not only that you have to do it yourself for every app and the tedium of having to reimplement over and over but there is the problem when you reimplement this yourself you will learn more as you go and the apps mature and features are released and there platform matures so then you have a bunch of things that are slightly drifted out of date. Each project you build has one new feature or one slight change or one lesson learned you corrected. You will probably never have time to fix all those things in every app you built so you have geological layers of decaying code as you go backward and you go you will go back to work on that project from three years ago and go I have to put all those things back in and you are manually comparing code to see what's different. It is such a game changer when you find a library that is compact enough that something like this, where if we implemented a search like this on a dozen sites, and we go from V4 to V8 updating 30 lines of code across a dozen sites is just really not that much. I think this is a small example but compared to the 2-400 lines of code I am going to write to build this myself and not even get close to what this is actually doing if we start thinking about local storage management, cache invalidation and all these things. I will never update the old code and it will decay until someone burns it down and starts from scratch.
DOMINIK: Pretty much. If the only thing I need to update is React Query to a newer version that's already a win. The only thing I have heard about React Query in a negative way is people tend to complain about the bundle size. It is not like -- the lightest library. It has, I think, it is a complaint that's kind of very -- how do I put this. Like it is a 10 or 12 KB library. It is not like nothing. But in terms of the value you get out of it I think it is totally justified. We are also trying to get lower with the value for the next major version obviously. When you think about the code and have 5-10 yearies -- queries, and in the cache management becomes complicated. If you have to do all that for yourself, you also have to write tons of code. This is where these solutions kind of start to pay for themselves because after a certain threshold, you know, the bundle size of React Query doesn't increase. It is the user line code that gets moreover time. With every query you use more, it kind of pays off for itself a little bit.
JASON: I think that is a salient point people don't consider when trying to reduce bundle size and only looking at third party libraries is what is the tradeoff in in-house code. I think this is like the curse of people saying well, we will just roll our own. It is always simple when you look at it in isolation to roll our own and as you start looking at the features that are required to make the thing do what you need to it to do you will absolutely write the same amount of code. It isn't like library authors are out there saying I want to add more code to this bundle. I know you are putting effort into reducing the size as much as possible. If you are using the features in the library, you obviously don't need to import moment because you are not using 95% of what moment does. You are not using 99% of what load dash does. With something like query you probably are. You are requesting data and invalidate your caches. I feel like I am going to write more code and my code is going to suck. If you are looking at not refetching data and what we are worried about is the impact on the end user and in terms of what they are transferring, not refetching your queries every time they change components or change views or reload the page, that's going to have a significantly bigger impact on the amount of data transfer then, you know, four kilobytes in the end bundle size. That's another thing to consider. What are you actually reducing bundle size for? To satisfy that competitive part of your brain that's like I can get the top score? Or to do something for the user and if so what are you trading off when you reduce the bundle size?
DOMINIK: With the fetching, in my opinion, always before we were using TanStack Query we were doing it wrong in both of the directions. We were either doing something like we fetch on mount of the app are redux and put it there and redistribute and never refetch and look at super stale data and users have to refresh the window in order to see the latest data. Then there was the time when we were fetching every time a component mounted. When you do it in user effect and it is kind of on mound of the component and you have a dialogue and the dialogue needs data every time you open the data you get the loading spinner, see it, fetch the data, close it, dialogue is gone, open it and see the loader spin again. We were underfetching on one end and overfetching and giving a bad user experience on the other end of the spectrum because you have to wait for the data you literally fetched a minute ago. This is where React Query kind of finds the bridge in the middle with the stale time validator approach and stale time you can set if you don't want to refetch that often because there are end points as in our codebase where we fetch something once and then we don't want it to refetch because it is something like an instance data or something that doesn't update that often. You have the options, right? Stale time infinity is also an option. That's the key to not make query refetch.
JASON: We have about four minutes left before I need to break us down. A couple questions I think are good questions. Is it possible to use query with next js13 and the new API?
DOMINIK: Yes, it is. It is an interesting question because like everybody as we learned about next 13 weeks ago by now but I wasn't prepared about all the things coming so we had to look into this. We have great people looking into this right now. There is an open draft pull request that kind of tries to update the documentation about server-side rendering to have the directory and how to do this with React search components. The easiest way we have found with what we know, because I think the cache API from React is still kind of the RFC is not out, from what we know now you can basically fetch in the server component, like just with, you know, either with query client fetch queer that gives back data or you do the good old await fetch something and then you have the data and you pass it down to a client component and that client component will then take this as a prop and call use query and pass the data in as initial data and that will probably get you a very long way with server-side rendering because you can co-locate this. This is the biggest win that you are getting with this because with the old pages approach you had to fetch for everything on that page at the very temperature top level. With the app directory you can have one component, fetch it on the server, pass it down, and you can also use it everywhere on the client. That is one approach. We are also looking into the whole hydration story with next 13. But there will probably be more things that will support this in version 5.
JASON: It is about the time I start asking you where people should go if they want to keep up with development. I am going to first throw everyone toward your blog. It sounds like at this point everything we have talked ability Fe about there is a blog on tkdodo.eu that covers it. I am going to also point folks to your Twitter. If you want to keep up with what I am doing, Twitter is still the best place.
DOMINIK: I did try out Mastodon but it wasn't for me. I have an RSS feed you can subscribe to for my blog otherwise I will post about Twitter. If you want to stay up to TanStack Query I totally encourage you to check out the Discord channel I have. I will copy the invite link. We are a very active community of developers and maintainers. There is also everything about the TanStack. Not just the query. If you go into the TanStack link at the top you will see that there is TanStack table obviously and the new router that Tanner is working on. There is the virtualization library we are also using at work. We have a heavy TanStack users at work and most of things happen on Discord as well.
JASON: Very cool stuff. I am attempting to twist Tanner's arm into coming on and teaching rerouter once it is ready so hopefully we will have an episode on that in the future. I got to see the demo at jam stack conf and saw Theo's overview video. It feels like something that is going to be magical when it is ready for prime time. Can't wait to play with that.
JASON: Anything else you want to shout-out before I tear us down?
DOMINIK: Just thank you for inviting me. It was a lot of fun to show the concepts of TanStack Query and I hope that everybody had fun watching the show.
JASON: It was an absolute blast. I really, really enjoyed having you on. I said it kind of tongue and cheek but I think it is true. This I think is the most polished episode we have ever made. Your preparation went a really long way. Thank you so much for that. Chat, we are going to find somebody to raid but before we do let's do another shout to the sponsors. This episode like every episode has been live captioned. Huge thank you to Maggie from White Coat Captioning who has been here all day and that is made possible our sponsors Netlify, NX, New Relic. We san incredible line up here and let me drop a link. We all going to look at developer training. If you are an intermediate plus dev the team at wilco is doing cool stuff and we will look at what it is and how you can build your own stuff. Daniel row is coming on to teach us about nux 3 and nitro. Really excited. It has been a long time coming and I think that's going to be really, really fun. Docusource and we will look at that. I am talking to other folks. It is going to be wonderful. Depending on which holidays you separate, I hope you either had a great holiday or are growing going to have a great holiday. We are going to find somebody to raid. Thank you for hanging out. We will see you next time.