All About React Query
with Tanner Linsley
There‘s a lot of buzz about React Query and how much it can simplify your development workflow. In this episode, we’ll learn all about it from the #TanStack creator himself!
Topics
Resources & Links
Transcript
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, everybody, and welcome to another episode of Learn With Jason. Today on the show, we have the maker of the TanStack himself, Tanner Linsley, thank you so much for joining us.
TANNER: Good to be here.
JASON: So I think that for those of us who work in React, we've probably heard your name across a bunch of different places. Like, in addition React Query, you've also made React Table and whole suite of tools that show up all over the place when you're looking for things to just react your React work flow easier, I guess, less painful.
TANNER: ( Laughter ).
JASON: But for those who are not familiar with your work, do you want to give us a little bit of background?
TANNER: So I have a startup called Nozzle, and that's where we basically reverse-engineer Google search ranks, provide that data to people. In building Nozzle over the last five or six years, I ran into a ton of things with React that like you said were a little painful, you know, the first library that I built that got some traction was React Table. Because building tables seems like something that should be easy, but it's not.
JASON: ( Laughter ). Definitely.
TANNER: Especially when it gets into data grid stuff. Sorting, filtering, pagination, it blows up in your face. It's been around for a while. I have a few other tools, React visualization, I did a bit of work in React js, I did a stint with a static site generator, with React Static, but I don't maintain that anymore. But my latest one is React Query, which technically turned a year old a couple of weeks ago, but it's been in the spotlight for about eight months.
JASON: No kidding. And so React Query, I guess to maybe start from, like, the broad view, right, like, so if I'm looking at querying data in React, you know, the browser has a built-in fetch API. So what am I doing with React Query that is different from the fetch API, or maybe a better question is, what is the fetch API not doing that React Query is helping me with?
TANNER: That's a good question. So you're using a fetch API, you're using the browser cache, you can use the browser header and save on bandwidth and speed, right, but that concept of caching doesn't carry itself all the way down to the user experience in your application. Like, even if you have to -- even if you refetch data and it's, like, right there at the browser cache, you know, a lot of the systems that we have in place in React, they're not going to, like -- I hate to use this word, but they're not going to suspend while that happens and make it feel like it's there instantly.
JASON: Right.
TANNER: And so first and foremost React Query is kind of like this really light caching layer that just lives in memory inside of your application.
JASON: Okay.
TANNER: It doesn't take over the responsibility of, you know, using the right cache headers and doing, like, receiver-side and browser-level caching. But it's more of a cache to improve the user experience. And possibly fill in some of those gaps. You know, if you don't know a lot about browser caching, then React Query can also help you a little bit there. So first and foremost, it's like a UX-driven library. It's built to improve the developer experience immensely.
JASON: Oh, we just lost your audio for a second. Oh, wait -- so it's built to improve --
TANNER: Built to improve first and foremost the developer experience and also the user experience working with that data.
JASON: That's an important distinction, because that's something that comes up a lot. We talk about user and developer experience and sometimes a tool will overindex on one or the other. Where the user experience is really good, but oh, my God, is it hard to build. Or you kind of go too far in the other way and the developer experience is tie dream and then the end product is just kind of a drag to use for the end user.
TANNER: Yeah, finding that right balance is difficult.
JASON: For sure. And so you mentioned a couple things. Like you mentioned caching headers. So React Query is going to deal with for us.
TANNER: Not technically. So React Query is really agnostic as to how you actually fetch your data.
JASON: Okay.
TANNER: The only thing it needs to know about is a promise.
JASON: Oh, interesting.
TANNER: So React Query doesn't necessarily know about the fetch API or any of the actual mechanisms or protocols that you're using. It just knows, you know, about asynchronous operation. That's really all that matters.
JASON: Fascinating. So does that mean that, like, I get to choose my data listening library, then.
TANNER: Yeah.
JASON: So if I want to use Axios, I can. What about server side rendering? If I pull in isomorphic fetch, will that work?
TANNER: Yeah, so the server side story for React Query is interesting. It has a great story for SSR that resolves around hydration the same way that you hydrate the React application when it gets to the client. Similarly, basically you fetch that data server side into a cache. And then you send that cache with your html and you rehydrate it on the client.
JASON: I got with you be I got you. I totally get that, that totally makes sense. Cool.
TANNER: Yeah.
JASON: So I have a million questions, but I feel like it might be easier to just look at this, right? Go ahead.
TANNER: One of them, too, while you were saying, oh, I can use whatever I want, you can also use GraphQL clients, which kind of opens up some interesting, like, architectures and discussion there.
JASON: Look at that. Absolutely. And I like -- what I like about that is that you're not enforcing an opinion, so I can hit a REST API, and I could even hit the file server or something, theoretically speaking.
TANNER: Yeah, anything that's asynchronous, you can hit.
JASON: That's super cool. That's super cool. Thank you for the sub, Ryan, nine months, time flies. So how about this: Let's switch over to pairing view, because I want to see this thing in action. And while we do that, let's take a second to talk about our sponsors. We've got live captioning today, I really hope that I fixed this. I did. I fixed it. Okay. Good. So we've got live captioning happening right now by White Coat Captioning. Thank you so much to who is in the chat right now. And that's made possible through the generous support of fawn a, Auth0, and Sanity, who chip in. You can find that at LWJ.dev/live. We've also got the video and team chat all at once. We're looking at React Query. All right. One at a time ready to roll. So where should we start if we want to give this thing a spin?
TANNER: Let's dive into that repo that you've got. It's actually set up to be kind of this dumb little blog example that lets you add and remove blog posts and kind of look at some of these things kind of in a transitional lens a little bit. So we'll start off with no React Query, and I can incrementally help add in some of these features to show you actually how it feels to use it. Instead of dropping a React Query example into your lap.
JASON: Got it. So let's see. I haven't done anything on this yet. So let me run the --
TANNER: Yeah, let's do a Yarn install.
JASON: So we'll install dependencies here, and then let's take a look at what's inside. So we've got an index page. And let's see. We've got like a wrapper. A sidebar. Admin section, blog section. And it looks like we have our sidebar here, our main here, and then some routing.
TANNER: Yeah.
JASON: So home page is a welcome page.
TANNER: Uh-huh.
JASON: And then we've got an admin page and a blog. And we can pull up individual posts in either editing or blog mode. So let's start this thing-up. It looks like we're going to use dev.
TANNER: Yeah, Yarn dev should do it.
JASON: And that's going to give us localhost 3,000, if I can paste it. I missed a letter. Okay. So we've got our home page. There's our welcome. Go to our blog, we're loading. You've got blog posts, yeah.
TANNER: Yeah.
JASON: And then if I go to to the blog's ID, we get the full content.
TANNER: Yeah.
JASON: Okay.
TANNER: Pretty simple.
JASON: And this is really straightforward, and as you said, this is not correctly using, like, let's look here. We've got screens, here's our blog, index, so we've got used posts and when we call usePosts, there's a hooks library, and we're loading it using Axios. So this looks like what I would do -- you know, I've stopped using Axios because I just, I don't know, I got converted to fetch at some point. But it looks very familiar in terms of how I would set up an app if I was going to do this.
TANNER: Yeah, I think this format is pretty familiar to most people.
JASON: Yeah.
TANNER: You know, that have fetched data in React.
JASON: Excellent. And this, then, I can see a couple things that I don't love: Like when we click through here, we can see that we have that loading screen, and then I click and I have that loading screen and now we've already loaded that data, so the fact that it's taking that much time.
TANNER: Yeah, that's really annoying. Uh-huh.
JASON: And then let's see. Our admin. We have the ability to edit, and then it looks like that if we pull it up, it will load. And we've still got that loading screen.
TANNER: And say we were in the blog post page, even in development here, if we were to go to the actual JSON store and change one of the titles for that blog, we'd either have to reload the page or unmount and remount the component to see that. It should be under store.JSON. It's just a little JSON file. But you were if change the title of that first post, it wouldn't make itself into the dev environment, even.
JASON: Until I refresh.
TANNER: Without doing some type of refresh or unmount and remount. You know?
JASON: Yeah.
TANNER: So ...
JASON: That's also a bummer.
TANNER: Yeah.
JASON: And you're saying -- so all of these things that we've just list South Dakota kind of our listed grievances. These are all things that React queries can help us with.
TANNER: Essentially, no one wants to use an application where every click, you see a loading bar for something. Especially when you're developing applications. That can get really annoying, because usually it's repeated actions. You're going between different places multiple times. You know? So it's every single time you go back to the home screen, you see this loading thing, and you're, like, oh, no.
JASON: Yeah, and I'm just laughing, because David is in the chat letting us know that both is loading and is success can be true at the same time. And he would like for you to see him after class. ( Laughter ).
TANNER: Tell David that's not possible.
JASON: Oh, no. Impossible states are impossible! But, yeah. This, I think, is -- this is going to be fun, right? So I'm ready. Where should we start, like, should we start on the blog page or somewhere else?
TANNER: Let's start on the blog page. And the nice thing, we don't even need to start on the blog page. We could start on that used posts hook. Because that's where all the logic is happening.
JASON: That's where the action is. So I'm in used posts.
TANNER: Yeah. So first thing you want to do is, there's two sides to React Query. There's queries and mutations. And we're going to talk about mutations later if we can.
JASON: Okay.
TANNER: But for queries, React Query exports a used query hook. So you can just import one of the named imports, use query, just camel case from React Query and then we're going to use useQuery to basically replace all of this code in here. I don't know if you have Intellisense on, but we can give it a sense for what it uses for parameters.
JASON: So I don't have it on, because the pop-up is really noisy.
TANNER: That's fine. The first parameter that useQuery takes is some unique identifier for whatever it is you're fetching. In the most simplistic examples, it could be just a string. For this example, just a string, I usually call it posts, that's what we're fetching. You don't need that slash in there, it could be just "posts."
JASON: Got it.
TANNER: The second parameter you're sending through for useQuery is going to be the fetcher function. So this is the asynchronous function that's responsible for either getting your data, eventually, or the Logan error, if something bad happens.
JASON: And is this in an object or just the function?
TANNER: It's just the function. As long as it returns a promise.
JASON: And so I would be like async, and then I would probably just copy this, right?
TANNER: Yeah, in fact, in this case, you wouldn't even need to do the async if you just wanted to do an in-line function that returns axios.get.
JASON: Oh, and we don't have to await, because axios.get will return a promise.
TANNER: Yeah.
JASON: And so we can just trust that JavaScript knows that async await is just on top of promises, and everything will work the way we expect.
TANNER: Exactly.
JASON: So all of this state business down below here is still important, but React Query useQuery hook handles all of that state. So you can get rid of that entire reducer, we don't need it anymore. The fetch function, don't need that anymore. And React Query also handles all of the life cycle around all of your fetching. So we don't need the effect at all. In fact, we don't need any of it.
JASON: Okay, so just -- well, actually, I guess, ...
TANNER: So the nice thing here, I kind of cheated a little bit with this example. I used the same return structure for that custom hook that useQuery returns as well. So you can literally just "return useQuery" here.
JASON: Oh okay! So if I take this out, then what we'll get is the spread state then would be the fetch -- or isloading is success.
TANNER: Yeah. So useQuery returns a query object, and it has all of that same information on it happen.
JASON: Okay.
TANNER: It's got the status enums for David so he doesn't have to freak out about booleans.
JASON: ( Laughter ).
TANNER: It has booleans. Just so David doesn't confirm, the booleans that you get back, they are stable. They can't get into impossible states.
JASON: Okay. So they're derived from an underlying status.
JASON: I got you.
TANNER: And it also gives you back the data property and the error property. And a couple of other properties that we can look at here in a little bit.
JASON: Okay, so I'm using this, and then if I go to my user page here, I get my query, so I'm going to console.log this to see what came back.
TANNER: Sure.
JASON: David is in support of derived booleans. So now we've got this object. So can fetch more, clear, we've -- so here's our post. Then down here, we've got, is error, is fetched. Okay. And then we've got this status. So this would be the more, like, if you're an ex-state fan, this would be what you would expect. Like an enum.
TANNER: Yeah, state is, like, what is the actual state of this machine if you want to talk in ex-state terms, right.
JASON: Very cool. And then we've got a couple of functions here. Refetch, remove, last updated, this is really slick, this is really slick.
TANNER: And honestly, a lot of this stuff you honestly don't have to interact with. It's all there as part of the public API but for the most part, I don't even really use much of it, other than is it loading?
JASON: So with what we just did, does that mean that if I click into one of the posts, and click back into the blog, I'm not going to see the loading screen anymore?
TANNER: Exactly.
JASON: Moment of truth. So if I did this right, no typos, we're going to see an instant load of the blog. Oh, look at it go. So we literally changed -- we actually deleted a bunch of code, which is wonderful. That is so nice. And, you know, we can even look at this -- here's our div here. We went from all of this code down to just this. So just the axios and useQuery, and that instantly improved the -- so this is a DX and UX win. Because now our page loads better, and we have to do less work. Everyone is happy.
TANNER: It's not even a net gain. It's a double gain. You're getting rid of code and getting better features out of it.
JASON: Hm-hm. This is excellent. I'm really into that. Then to do the same thing with the use post, less see if I can do this. I'm going to import useQuery from React Query, and then down here, we've got our post ID, and that just returns axios. And so I'm going to and it looks like this one has the same return value, so I get to delete all of this stuff. And I'm going to return useQuery and we'll call it, I'll probably just use this postID, like that seems like a reasonable unique identifier.
TANNER: I'll give you a little hint after you get done here on the key, but keep going.
JASON: Okay. So then my second argument is to have a function that will return fetchPost, postID, and then I can delete this bit. This should just work, right?
TANNER: Yeah.
JASON: Okay. So what could I have improved here?
TANNER: So this will definitely work. Like, this is a unique key for the post itself. Right? But for the future, we're going to add some, like, some organization to this key a little bit. So you can actually pass an array as a key.
JASON: Interesting.
TANNER: And instead of just a string, we're going to kind of create some hierarchy here. So we're going to call it posts and then postID.
JASON: Does that get serialized for you?
TANNER: No, it gets serialized for you under the hood, and it's kind of magical.
JASON: So what's the benefit of doing it this way instead of another way?
TANNER: I'll have to show you in a minute, but it comes to how can we manually invalidate some of these queries on our page?
JASON: Okay. I got you.
TANNER: If I were to ask you, which one is easier to remember, this kind of path kind of-based string here or kind of a structured object. Most people are going to say, "Oh, I can remember the structure. And it's easy to turn that ( off mic ) somewhere in your application. There's some other benefits to it too. Some things like prefix matching. If we wanted to refresh all of the posts, queries, on our pages, you know, you could do something like invalidate queries' posts. And that's going to invalidate like every single thing that matches posts prefixes.
JASON: That's really cool. ( Simultaneous speech ), yeah. Okay. So a quick note for the chat, Q&A is the whole time. If you've got a question, fire it off in the chat. And we will try to answer them in line. Keep in mind, if the questions go too rabbit holey, we'll probably push them off to Twitter or something else. But, you know, definitely do that. So, yes. The code about invalidation, we're going to get, we're going to get to that. But let's circle back around, though. I want to see -- I want to make sure that the posts -- the post query is working.
JASON: Yeah, so as you were explaining the invalidation, I was clicking in. Now we've got an instant load into the post, and one that I haven't tested yet, it's -- I click back in, there it is. Instant. So now we've got this amazing experience of very quickly loading all of our posts. And then if -- I didn't ask about this, and I didn't look at the code yet, so I don't know if this works. But in the admin section, we've already updated this, right? Because does admin use the usePosts hook to load those posts.
TANNER: Yeah, it does.
JASON: So it should be instant, and I haven't changed this at all. I love it when things get easy and I don't have to write any code. ( Laughter ).
TANNER: Exactly.
JASON: That's beautiful.
TANNER: It's great.
JASON: And this is really nice, because I feel like this is something that we've all dreamed about. And this is something that a lot of libraries kind of promise, right? You see a lot of frameworks and tooling about how they're going to do this deduplication, do this cache management, do all these things that will make your developer experience really easy. But typically, that comes with a lot of boilerplate, it feels like you're setting up a lot of stuff upfront to get that to work. And what's interesting here is, we didn't have to do that. Like, are we -- is there a context provider somewhere or something that's keeping this stuff globally available? How is this actually working?
TANNER: Yeah. So we're in V2 right now. This is V2 of React Query.
JASON: Okay.
TANNER: And in this particular version, this is just kind of a global Singleton for your app that you can just start firing off queries and everything just works. Context is not required.
JASON: Interesting. That's even cooler.
TANNER: And it does scale pretty well. In V3 that's coming up here in a couple months, probably, to help out some of our larger customers, ( laughter ), we are adding kind of a provider, like a query client kind of a provider.
JASON: Okay.
TANNER: It'll probably feel a lot like relay, you know, or something like that, where you provide the client to your application.
JASON: Yeah.
TANNER: And it will be using context under the hood to share that client. But at the end of the day, we're not using context to trigger renders or anything like that. It's kind of just all homegrown.
JASON: I gotcha. Cool. Okay. So right now, I mean, we're, what, we've been writing code for less than 20 minutes. And we just made huge front end improvements, and also, managed to delete, like, 50 percent of two files. ( Laughter ).
TANNER: Yeah.
JASON: Which feels pretty good.
TANNER: And that was just the hook versions of these files, right? Like, most people will not stop there. You know, if you want to get into some of these types of features, you can't just write hooks like that that use useEffect and state. People go into, do I get into Redux, usually they search for a global state management solution because they need global state because they need to be able to share all this cache, share all the data around. Whereas global state managers themselves, they're more -- they afford more to client state management than actually being able to manage all of these, like, unique server state life cycles. You know?
JASON: Okay. And I think that's why people get into trouble with global state managers. If you're using it just to manage asynchronous data, you're basically going to end up writing all of the logic that has gone into React Query on your own. And it gets really messy.
JASON: Yeah, I can imagine. And that actually kind of ties to a question here: So Nickie in the chat is asking: Can you combine this with something like Apollo or does this replace something like Apollo?
TANNER: So I would say it probably replaces Apollo.
JASON: Okay.
TANNER: More than -- you could use it with Apollo ---like the client that comes with Apollo to fetch stuff from GraphQL. But at that point, I would just say, use a package like GraphQL Request, or even just use fetch.
JASON: That's actually how I send a lot of my GraphQLs requests these days is the GraphQL API.
TANNER: It's super easy. And the only thing you're not getting with React Query is the normalized database for your individual query fragments under the hood. And to be honest, most people don't even know what data normalization is doing under the hoot. And a lot of applications don't really benefit from it. Or they're not -- it's not crucial, you know? So unless you're Facebook or some really complex application that's built on, like, GraphQL in its entire ecosystem, I don't see a big reason to use something like Apollo or relay unless you have really, really good reason to. You'd be better off probably just using React Query with fetch, you know, or something like GraphQL requests, in my opinion.
JASON: Okay. And then another question, and then we'll write some more code here is, would you use React Query to manage auth state?
TANNER: Many people do. It just depends a lot on how you do authentication.
JASON: Okay.
TANNER: I have some auth state in one of my apps that's really simple and, yeah, I just use React Query to kind of keep it up to date. I have another auth system that uses fire base auth, which is a big hairy monster. And I don't even bother. I have a custom Auth provider for that.
JASON: I gotcha. Cool, well, I want to go deeper. E sure.
JASON: What should we do next?
TANNER: Let's do it. Let's do something that's kind of fun. Before we go over and get into mutations, why don't we head back to the usePosts hook.
JASON: The singular or plural?
TANNER: The singular version.
JASON: I'm in it.
TANNER: And I'm going to show you a little trick here. I'm going to explain it as I do it, if that's okay.
JASON: Go ahead.
TANNER: Remember the global state, you're, like, where is all the stuff getting stored? It's getting stored in this query cache. And something interesting that happens is when we look at a new individual post, we still get that hard loading state, you know, but if you noticed, we've already loaded that post in the parent query.
JASON: Yeah. That's true.
TANNER: Or at least part of it. Right? Part of it is there. The preview, I guess, you would call it. So we should be able to use that, and the good news is that you can. So I'm going to take this query answer the question that has all of our data in it, or should, right, and one of its methods is, called getQueryData, and I'm going to pass it the key for our parent query. And that should technically give us -- in fact, we console.log that out, if we head back -- oh, sorry, fetch post, initialData is the function I want to run it in. Okay. So if I head back to our page, and let's just go visit an individual post and see if we get something in the consul. So let's go to the blog, click here, pull up the console. And I think this was it. Maybe I should take out the.
TANNER: Yeah, perfect.
JASON: Let me get rid of the part where we're logging the rest of the post so Benjamin just just the one that we're looking at.
TANNER: So it's interesting. We loaded it straight into it, and it was undefined, right?
JASON: Yeah.
TANNER: And that was because that was the first page we loaded. There's not much we can do there. But if you're going from a blog into an individual post -- yeah, so we see that data. What we're going to do is, we're just going to try. If it's possible, we're going to try to find the post in here that corresponds to the one we're trying to fetch.
JASON: Okay.
TANNER: And we're going to return it as initialData.
JASON: So we've got the query cache, and then getQueryData is a built-in to the query cache, and we use the queue of posts, which is the same one we used in posts here. So if we change this, then we would change this, those have to match, and then when we do our find, we're going to get the entry, so this is an array, right, so doing a find on our array, and check in the ID matches the post I had. This is what you would do with an array, you would look up to see if you've got the thing. And so once we saved this -- so theoretically speaking -- so I'm going to refresh so I get my loading. And then if this did what we want it to do, we shouldn't see that first loading screen when we click into one of these posts because it's already in the cache, right?
TANNER: Precisely.
JASON: Pffff. Look at that. That's magic. But again --
TANNER: So there's something else to note here. You can see that the version that you were seeing on the preview, though, so let's go back and do the initial data flow again. So blog, refresh --
JASON: Okay.
TANNER: -- click on an individual one, and just leave it sitting there.
JASON: Oh, there we go.
TANNER: You'll notice there's a little ellipses from the previous one. Because you don't want to fetch all the data from an index post.
JASON: That makes sense.
TANNER: If we go back to a -- we're going to pass initialStale: True. And this is just a little hint to React Query that what we're passing as initial data is kind of more like a placeholder.
JASON: Okay, so I load, I click, there's an ellipses, and then it loads the new thing. That's slick. These were patterns that I like I always want to build and I never get around to. So this makes me really happy. ( Laughter ) it's really fun. And we're not going to do it, because it takes a little bit of time. But you can actually do the reverse as well. So if you went into, like, the used posts hook, you could take the data from useQuery right here and -- sorry, it's the multiple usePosts, you could take the data here, and say, onSuccess, I want to loop through the data like this. And grab the query cache and say, setQueryData, and we could do something like posts, post.ID, post.
JASON: No kidding. And so then that would already work when we went into one of these.
TANNER: I basically just wrote it, so let's see if it works.
JASON: ( Laughter ). So then would we need to take it out of --
TANNER: Yeah, so let's take this initial stuff out of here.
JASON: Okay.
TANNER: Yeah.
JASON: So go back to the blog. I'm going to refresh.
TANNER: Hopefully, this works. I don't do this one as often.
JASON: Oh, it doesn't like setQuery.
TANNER: Oh, my bad. It's setQueryData.
JASON: There we go.
TANNER: Let's try again.
JASON: I'm reloading, there's our initial load, and it gave me a load screen. So I don't think it quite took that.
TANNER: Maybe we have to do the stale team to be a little higher, something like five seconds. Let's see if that fixes it. This flow is not as vetted, I guess you could say. It's okay.
JASON: It's easy enough to do it the other way.
TANNER: We have docs on how to do that. But you can see how flexible it gets. You go, "How do I want to approach these optimistic fetching strategies?
JASON: And this is so nice that it starts with the placeholder, and pulls in everything.
TANNER: And that's going to get better in V3, too, a lot of fun things are going to happen in V3.
JASON: Very cool.
TANNER: Before we go any further, let's -- I want to show you something that's probably going to blow your mind. It's going to be really fun.
JASON: It gets better, cool?
TANNER: So head back to the index page, where we've got all of our routes.
JASON: Index page, this index page. All right.
TANNER: Down here, after the wrapper, or -- right after the wrapper closes --
JASON: Right after the wrapper closes. Okay.
TANNER: We're going to render a new component after here.
JASON: Okay.
TANNER: And let's see if I can find you.
JASON: I'm under the wrapper post here. Line 44.
TANNER: Here we go. So we're going to render the component ReactQeryDevTools and then come up top here and render that. So impact ReactQueryDevTools from ReactQueryDevTools. Let's go back to the app.
JASON: Look at this. Oooooooh. So then I'm going to click into one of these, I'll click into this one. Oh, that's slick. And then we can pop this open. And see what's in it. Dang.
TANNER: So this kind of gives you like the visualization of caching, which is a hard concept to visualize, if you think about it. But on the left there, you've got the list of all those queries that we've been running. And you can see some of them are great day out, which means they're inactive, because we're not subscribed to them on the page right now. But they're in the cache, and they're technically still fresh, right?
Now, you've got one that's stale on your page right now, post one. And so we start learning about this concept of stale versus fresh. Out of the box, React Query is very aggressive, so it's kind of always refetching your data in the background. Let's go back -- let's go to an individual post. Just pick the first post as an example.
JASON: Okay.
TANNER: And let's go into the editor, right into the store.
JASON: Editor, into the store.
TANNER: Yeah, and just go ahead and change the title to something random.
TANNER: Corgi. Did you see that?
JASON: So did you see that approximate it showed me fetching, and I have not refreshed this page, and now it's live.
TANNER: Try it again, without the dev tools open. Let's see what happens.
JASON: Dang. Okay. And so is this under the hood, is this pulling?
TANNER: No, so it's using your interactions as a user to kind of hint to React Query when things should be up to date. So one of those hints is a window refocus event. So when you refocus the browser, it's detecting, like, hey, you've come back. We should probably update things that are stale or need to be updated in the background.
JASON: Got it.
TANNER: And it does it without flashing reloading in the background. It just kind of in the background, updates.
JASON: That is super slick. And I thought I was trying to rush to get back before it finishes refreshes.
TANNER: Pulling can be expensive.
JASON: That's what I was thinking.
TANNER: Honestly, I use Web Sockets in my applications to get specific events and tell React Query to kind of invalidate things.
JASON: Yeah.
TANNER: But the onWindowFocus stuff is really nice. You don't have to do anything. It comes with it.
JASON: So you just said something that piqued my interest, and I just want to ask about it -- we don't have to rabbit hole here -- but you said that you're using Websockets so work with React Query. So does that mean that I can do something like a subscription with React Query?
TANNER: So React Query doesn't treat subscriptions as first-class things right now. Because you don't really need to. The mechanics of subscriptions change a lot.
JASON: Sure.
TANNER: You could use Firebase or Pusher or so many mechanisms out there. But really, what matters is just, can you respond to an event coming in?
JASON: Right.
TANNER: And with React Query, it's really easy. You just have to have the query in hand, and you can respond two ways, if your notification from whatever this subscription is, has the payload that you need, then you just use the query cache, set query data, and it gets updated and everything on the page updates. If you're doing more of a poll model where the notification is just telling you something has changed, instead of doing the set query data, you just do, invalidate queries, and so you just tell your queries to go and refetch.
JASON: And that, invalidate queries, that's just something that's going to come back from the query itself, or is it -- or is that, how we imported the set query cache, or --
TANNER: In fact, let's get into that a little bit. How are we doing on time? We've got 45 minutes, plenty of time.
TANNER: Perfect, let's get into mutations, because I think some of this is going to be clear if we get into the mutation part of it.
JASON: So to look at how this works, I've got our admin post here, I'm going to put a test post here, and I'm going to say, "What up chat? This is the best." So I'm going to save, uh-oh. What did I do?
TANNER: That's totally fine. That's expected, because right now, if you go to the admin screen, go to the index for the admin screen,.
JASON: Admin index.
TANNER: You'll notice something, on submit, we're waiting for create posts to finish. And before, when we weren't using React Query, we had to kind of tell this post query to go manually fetch again.
JASON: And so that was why, when we look at this used posts, or use posts, I should say, there's an export.
TANNER: Yeah.
JASON: And this one, we don't have anymore. We're not exporting.
TANNER: Technically, React Query does have a refetch function on it that we can use. I'm back in index. So instead of the old fetch function we were exporting before, React Query has a refetch. And this would totally work. But what this means now is that everywhere we use our posts query like in a component, we have to remember to refetch it after something like that happens.
JASON: Okay.
TANNER: I don't like that. I don't like having the responsibility to remember when to do things in a lot of different places.
JASON: I'm noticing a pattern in the way you write code. ( Laughter ).
TANNER: Let's take this -- ( laughter ). Let's take this out for a second.
JASON: Okay.
TANNER: Because really, we're not going to need it. And really, this doesn't have to do that now. And honestly, this is just an identity function. And so instead of onSubmit, we could just do, createPost right here.
JASON: Got it.
TANNER: Instead of onSubmit. So let's go into our useCreatePost hook, and let's just take a look at it.
JASON: So here's useCreatePost.
TANNER: It's very similar to what was happening in the other one. We're managing state. Instead of firing something off automatically, we're just returning the mutate function that we can use to pass it some values and have it go save it on the server.
JASON: So mutate when we use this, if we look at the index, we are importing, this is just an alias for the mutate function.
TANNER: Yeah, exactly, following that trend from React.
JASON: I do really like this, because then you can use multiples, and then they would all get different names, I don't like the destructuring alias thing, because it's hard to read. So it's nice to return a array, where you get to assign a name instead.
TANNER: Some of that may change in the future, I know that it will, because we're moving React into a framework agnostic position, but for now, it's a really nice API.
JASON: Yeah, and so this is really slick. So if I want to refactor this, then theoretically speaking, I'm going to import --
TANNER: This is what I was waiting for.
JASON: I have a theory, I can use usemutation instead of use query.
TANNER: This is how APIs should work, you learn one part, and you're, like, "I bet I could do the same thing with another part. I bet it works the same way."
JASON: So it's going to have a unique identifier, I assume.
TANNER: It actually doesn't need one.
JASON: So no unique identifier.
TANNER: No, the first thing you're going to pass is the function that's going to perform the mutation.
JASON: Got it. Okay. So then we will take this part. And pass that in. So let's break this onto a couple of lines. And then that's probably it, isn't it, to start?
TANNER: Yeah. All of that other code can die. ( Laughter ).
JASON: Get on out of here! So that's gone. Okay.
TANNER: And one thing to not miss here is that mutations usually take variables. So you're going to want to add that variables right there.
JASON: That's right. So this would be just an object. Right?
TANNER: Yeah, the posts that we want to create.
JASON: Cool. So we've got useCreatePost, and here, nothing really changed because be useMutation I'm assuming is returning the signature.
TANNER: It is, we're cheating there again.
JASON: That's good, that's a good teaching tool, instead of having to explain why it was one way and not another. So this createPost info, that's going to be the response like success or the post that was created or whatever your decision is from your API.
TANNER: It's got all the state, so it's got the same isLoading, isIdle, isData, all that good stuff. But we're using it down in that button to hint to our you're what's going on.
JASON: Excellent. This is great. So then theoretically speaking, if I want to reload, save, oh, did we not delete that part.
TANNER: Maybe I didn't hit save. Or was it use Posts?
TANNER: It was in the admin index, actually.
JASON: Oh, oh, oh. And that's gone now, right? It should be. Maybe we just needed to do a hard reload. I don't think that code is anywhere now.
JASON: So I'm reloading. Save. What's trying to --
TANNER: Where is that coming from?
JASON: Oh, admin post. It's trying to call in here.
TANNER: Oh, we're on a individual post. We've got it.
JASON: So I'm just going to delete that.
TANNER: Okay.
JASON: ( Laughter ).
TANNER: Now we can have the climactic reveal.
JASON: Now that's saved, I can come out.
TANNER: Go back and let's do that again. You'll want to look at the title. Because -- when you do that. Because the form that you're editing is just kind of like a draft of what you're editing, so edit the title again and hit "save" but scroll up to the top of the page so we can see the title.
JASON: Oh, yeah. So it didn't change.
TANNER: It didn't change.
JASON: And this is where that fetch needed to happen. Right.
TANNER: Right.
JASON: But like I was telling you, you should have to remember to do that everywhere. You shouldn't have to remember to do that everywhere. You should only have to remember to do that one place. And that should be that useCreatePost mutation that we talked about.
JASON: And so if I go in here, then I'm going to assume that I've got the ability to pass some kind of like a cache update ter.
TANNER: Bingo. Yes.
JASON: Okay.
TANNER: So on here, the call backs for use query and use mutation are very similar. It's onSuccess, on whatever. So in this case, it would be just be onSuccess, and it's just a function we're going to fun.
JASON: Okay.
TANNER: And this is where, like, this is the first official entrance of the React Query querycache into your code. So we want to refetch that post query. We need a way to communicate from that mutation over to that individual post query that it needs to refetch. And that's where the queries come in handy. So we use the invalidate queries function. The method off of the query cache. And you just have to match up the key. So posts and postID.
JASON: Now, how are we -- how are we getting the post ID?
TANNER: Where does the post ID come from?
JASON: Well with, the post IDs come from two places. The data coming back, if your API comes back from the data, then you could use it there. But this is a createPost. You know? Or sorry, this isn't createPost. Does this use save post? We have been editing the wrong ones.
TANNER: Oh, we have, haven't we?
TANNER: Let's go over to use save posts and bring our work with us.
JASON: Okay.
TANNER: So it's this axios patch right here, it's the stuff that we kind of want to save. So let's bring that up here.
JASON: On I'll copy, go into save post, drop this, and then we'll take this part, and let's just replace.
TANNER: We were just mismatched on, yeah, there we go.
JASON: The story of my life, editing the wrong code. ( Laughter ).
TANNER: Perfect. So this is useSavePosts, right, this is what we were doing, and it would have worked the same way. All of the demo that we have done is still worth it.
JASON: Yes, okay. So this is where, again, we can use the -- this is very standard, people returning the data that you get from the patch. So there's error -- sorry, there's data, and then there's also the variables that we're sent with the mutation. So we could do it from the data or the variables.
TANNER: Hm-hm.
JASON: In this case, it is sending it back, so we can just send back the actual data.ID. So this is actually the post that we're getting back. Okay, nice. This all makes sense to me. So then we can save this and then this is going to be the same thing, right? It would be post, or there would be no cache to invalidate?
TANNER: There isn't an individual post to invalidate, but when you add a new post, you're going to want to --
JASON: You're going to invalidate the whole thing, right?
TANNER: You could either do the array version or the string version. It's technically the same thing. But ...
JASON: Perfect. So we don't need that, so we can leave that. Okay. Let's give this a shot. So now it's updated. Let's go with a sixth time. I'm going to save. Uh-oh, oh, I forgot to import it. So that's fine. We're going to use save posts and then I'm going to import queryCache. All right.
TANNER: Yeah.
JASON: Then let's try this one more time.
TANNER: But these are good errors. It's, like, oh, I know exactly how to solve that.
JASON: I like it, because it -- there we go. Now it's updating properly. And if I go back out --
TANNER: Yeah.
JASON: We know it's there. Slick. Okay.
TANNER: And in the admin too. If you're in the admin, it's the same exact data.
JASON: And I haven't had to refresh the data. This is all --
TANNER: Try the -- see how that works.
JASON: I'm going to turn off. Let's create. Look at it. Immediately.
TANNER: There you go.
JASON: Immediately available. And notice, I just clicked through, so I went admin, into the editor, then I went to view, and I never had to reload, like it all just worked. It was all there.
TANNER: It's almost -- it can almost be jarring as a developer if you're used to your application having loadings everywhere where you get into React Query and they disappear. It's kind of like a, wow. That was fast. Was that supposed to work that fast? It almost feels like you're doing something wrong. But it's all amazing. So there's a question in --
JASON: So there's a question in the chat about using the .then. So we write this here, where we've got axios, we're here. So why wouldn't we do like a revalidation here? Like, ...
TANNER: You could. You know, these onSuccesss, these life cycles here, they're just there as a convenience. You could write all of your asynchronous logic in the function, if you wanted. But there's --
JASON: We could just bring this right up into here and it would be okay.
TANNER: There's a reason, though. People easily get asynchronous flow wrong a lot of time. Even I do. And these callbacks, while they feel like, "Why do I need a callback to do async logic, and some of it, you get to opt in only to specific parts of it without having to write all of the flow.
JASON: Jordan in the chat mention, if you used .then, you also have to manage error states and make the data came back.
TANNER: We could opt in to an onError here. We could do something like onSettled, which is just, give me the post or the error, you know, whichever one it happened to be, you can handle it all in one function. And, in fact, there's also another life cycle that happens with mutations called onMutate, which let's us do some really tricky cool thing, if you're interested.
JASON: Oh, interesting. So, I mean, we've got 30 minutes on the clock. So we can dive into whatever you want here.
TANNER: Okay. Let's add a quality of life improvement to our application.
JASON: Cool.
TANNER: Let's go to the index page.
JASON: Index.
TANNER: And it doesn't really matter where we put it. So let's just put it up here by sidebar. All right, we'll do it -- all right. We'll do it above sidebar.
JASON: And I just have a component here that I've called global loader, I think it's global loader. It's -- global loader. It's in the components directory. So you can just -- it's the default export.
JASON: Default, got it.
TANNER: So import global loader from components, global loader.
JASON: Okay.
TANNER: And then let's head back to our application, make sure that that's showing up. It should just D by default show up in the top right-hand corner of our app.
JASON: Here?
TANNER: Yeah, right up there.
JASON: And now let's go into global loader, just to check out what's going on in the code.
TANNER: I'm going to change it to fix. So to preface this a little bit -- in fact, I should have showed you this first. Let's go to the blog index page.
JASON: Here?
TANNER: Yeah. Now, we know that React Query is automatically fetching in the background, all the time. And it would be nice to know when that's happening. So you can actually use the postQuery.isFetching which is different from isLoading, is loading is one of those Enum states, it's a hard loading state that doesn't have data. This could be happening in any successful stage of your application.
JASON: Right, that's the background, right, it's like, I'm checking to see --
TANNER: Any time that it's fetching. And you could show, hey, let's show some loading when that's happening.
JASON: Right.
TANNER: Now any time you go back to the blog page, it kind of shows you when it's loading. You can do this if you're doing it as a small, little app in every single place. But what I've seen is it's kind of nice to have a background loading indicator overall instead of having to do this in, like, every single template that you own. So instead of doing that one in the blog and every single other page, let's go back to this global loader that we were in, and we can actually import use is fetching from React Query.
JASON: Okay.
TANNER: Really simple hook to use, it's fetching equals use is fetching. If it's fetching, we'll show it. Otherwise, we won't. And let's do this.
JASON: Yeah, just to make sure.
TANNER: Okay. And so now, what should happen here -- so we save that.
JASON: Yeah, we see that kind of come in and out. So I click, come back up. There it is. And note, like it's happening for the blog now. It's happening for the admin section now. It's happening for the page edit. That's slick. That's really cool.
TANNER: It's a fuzzy feeling, like, oh, we're synchronizing with the server. And that global loader can give us some visual indications with what we're about to do with the mutation stuff.
JASON: That's really nice. And so I see a request in the chat to the optimistic updates. Is that where we're headed next?
TANNER: Yes, that's exactly where we're going.
JASON: Let's do it.
TANNER: So optimistic updates can depend on -- a lot of it depend on what your API can do. And it also depends kind of on your confidence level of your API and your data and what's happening. So I'm going to kind of show you on the spectrum of easy to more difficult/risky how you can kind of balance that.
JASON: Okay.
TANNER: I think we should go over with use saved posts. Let's do the post saving first, it's a good place to do. So optimistic updates, there's two ways it. The slowest way to synchronize is to inVal data queries. Because that means that your -- it's two round trips. Right?
JASON: Right.
TANNER: If you can cut that down to run round trip, that's a lot better. So if you know for a fact that this post coming back from your patch is the exact post that you would get from your query invalidation, then instead of doing the invalidate queries, you could just do, set query data, posts, post ID, post.
JASON: Okay. All right.
TANNER: Now we don't even need to invalidate that post, you know? We will still probably want to invalidate all of our posts, though.
JASON: So hold up. When I do this, okay. So that didn't -- I think I did that wrong because it looked like it still loaded. E oh, it's because I'm invalidating queries posts. Let's take this off for a second. Now, try it.
JASON: Okay.
TANNER: Let's see if it works.
JASON: Okay. So it happens pretty quick here. Did I miss a step?
TANNER: We shouldn't have invalidate queries going off anywhere. We're on useSavePosts, right?
JASON: UseSavePost.
TANNER: Oh, there we go. That seems to work.
JASON: It did work? How long should the take?
TANNER: That's the length of time to run the mutation. I was expecting it instant. That's my fault.
So as soon as the mutation comes back, that title should update.
JASON: Gotcha.
TANNER: You should not see a loading up in the top corner.
JASON: So this is what will happen. This button is going to show us how long the mutation takes to run -- how long the mutation takes to run. And as soon as it finishes, this should run. Whereas before, the mutation finishes, and this would have to send off a query to refresh, and we get double the load. So when I do this, edit, watch this button, it says saving, and as soon as it says saving, it's updated. That's our immediate update.
TANNER: We went down to one round trip.
JASON: Because we know, this will not happen unless that mutation succeeded, right? And as long as you know that that post coming back is the same post that we'll get, you know, refetched on this query. And so this is less of optimistic updating, and more cutting out extra work.
TANNER: It is not an optimistic update, but it's just kind of utilizing what you have in front of you.
JASON: Right. Right.
TANNER: Now, another level of this is, if you were to set this query data here, it's instant here. But then if you go back to the main blog list, that post title will still be out of date in the posts lists.
JASON: Right.
TANNER: Because they're separate queries. And that's where you can choose, they're not on that page, so you could just invalidate. But if you really wanted it, you could do it for the main posts query too.
JASON: And you could just kind of loop through and find the post and replace its data.
TANNER: Well, it takes an upright syntax, just like React state does. So you can say, return old map and then you say, you know, if d.id equals the postID, then we're going to return the new post, otherwise, we're just going to return what was there.
JASON: Right.
TANNER: And now you don't even you don't even invalidate queries, because now we're optimistically updating -- not optimistically updating. We're short-circuiting. Right?
JASON: Right, right. So let's save, it says updated. Go to blog. It's updated. And we never saw the loader. So that's slick, that's really nice.
TANNER: Exactly. And that's not even getting into optimistic updates. That's just kind of normal. Right?
JASON: That's just, like, I guess the efficient way to do this. It's less work to get the desired result.
TANNER: It's less work on your bandwidth, right, and that's just up to you. I think people overobsess about the bandwidth. If the uterus refreshes the page, you've got to make those requests. And if your application cannot handle your user refreshing the page every five or ten seconds, you have bigger problems on your API side. But it's true that we can save on bandwidth where possible with low bandwidth devices and things like that.
JASON: And for simple things like this, where it's a single list, it makes sense. If you had posts, and you had also a query for comments and likes, you're probably doing more work to, like, manage those caches together than to just invalidate the posts, right.
TANNER: Right. Exactly. And now we can get into an optimistic update. What is an optimistic update? It's where you are taking action on the user interface before you actually know if it's been persisted to disk.
JASON: Yes, and this is something that I'd never really heard about this until GraphQL started to sort of take the world by storm. Optimistic updates were something -- they probably existed before, but they were popularized by GraphQL.
TANNER: Absolutely. And so with user mutation, it's pretty easy to do an optimistic update. We can use the onMutate life cycle callback. And this just says, it's basically when we call the mutation function, that's going to trigger this, this is going to run first. And you get the variables that get called with it. So this would be like the new post.
JASON: And these are the same?
TANNER: Yeah. Exactly.
JASON: These are the same.
TANNER: Just for consistency, let's change, let's update these to new post.
JASON: And this one too would be new post. Or.
TANNER: Yeah, yeah, we could do that one too. So values, values, whoops. New post.
JASON: Okay.
TANNER: So it's all the same. So with this onMutate, what we're going to do, is we need to -- first, I'm just going to show you how to just update, update the data, right, and it's actually really easy, because it's just this. It's all this same stuff right here. You know? So we probably don't need to optimistically update the main post list, because it's not on screen.
JASON: Sure.
TANNER: But this one, we can optimistically update the individual post ID for sure.
JASON: And then do we need to do this one again, or can we leave that out?
TANNER: We can -- well, there is a chance that the payload you send is different than the payload you get back. Because if we were doing a huge createPost where there's validations.
JASON: We would get autogenerated IDs, we would get time stamping.
TANNER: If you're doing optimistic updating, you either want to utilize what you're getting back, or at the very least, invalidate afterwards.
JASON: Gotcha. Okay.
TANNER: So if we run this, let's just save this, and run it as is, and let's just see what happens.
JASON: So I'm going to in here, I'm going to grab this one, and we'll say edited, and save, oh, we get an error. What was our error?
TANNER: Post is not defined. We, I think we edited things.
TANNER: Oh, new post. There we go.
JASON: Back again. No, no one, chat? Okay. All right. So let's edit it --
TANNER: I was lagging a little bit on the video there.
JASON: Look at it go. What was our error? Cannot read property "map" of undefined. Did we map here?
TANNER: Yeah, it is possible, where we loaded straight into the individual post, we'd never made a query.
JASON: Exactly.
TANNER: So if you could do, if there's nothing in there, you could just return old, which would be undefined. You could also do this, where you can say, if query cache.getquerydata posts, if we have query data for posts, then we'll perform this operation.
TANNER: Okay.
JASON: So this is logical. Right. This makes sense. Where we're saying, if we've queried for the post, this one, we know we have. Because we are -- well, actually, we're just replacing this data wholesale.
TANNER: Right.
JASON: So there's no kind of updater failure there.
TANNER: Yeah.
JASON: The other way we could do it as well, is you could just default old to an array, if you wanted.
TANNER: You could be, like, if this doesn't exist, let's just make it an array.
JASON: And this would --
TANNER: And you could just say, new post.
JASON: Yeah.
TANNER: But really, the efficient way to do that, would be the if, right, so it's, like, if it has data, then we're going to do that, otherwise, we're going to basically just set query data post to, like, you could just include it to of your new post -- include it to have your new post. Right?
JASON: ( Laughter ).
TANNER: This will work, this will work.
JASON: Okay. So in this to just kind of like talk through this code and make sure I understand it. What we're saying here is, if we have an existing -- we're going to replace the old query with the changes we've just made. Otherwise, if we haven't hit post yet, we're going to create a whole new entry for post, which is an array that has only the posts that we've created.
TANNER: Yeah, and that scares me. This is all just risk management. This scares me a little bit because I wouldn't want it to stay that way. So I would also do, like, an invalidate queries here on that main posts. We're setting it optimistically so that it looks good when they go back to the posts page. But I don't want it to stay that way. And so luckily, when you invalidate a query that's being used on the screen right now, it doesn't show it. It kind of marks it as stale. So that the next time it kind of gets shown on the screen, it gets refetched.
JASON: It. So we can test it. Do we need to make these changes in createPost as well?
TANNER: Yeah, but they'll be slightly different.
JASON: Okay. Let's see. Yeah, we'll just make this happen.
TANNER: And we're also only halfway done with the optimistic mutation too. The optimistic update. There are some things we still need to iron out.
JASON: Ah, behold, my bucket! ( Laughter ). Okay. So we've got -- chat, I see you're playing with sound effect right now. All of the exclamation point redeem, those are gone. So it's only the other ones. Which if you want to see them, you've got to look at the Learn With Jason repo, because I haven't had a chance to fix them all. They should all be in there, though, they're in the Learn With Jason Github repo under functions. But, yeah. All right. If we update, save, now there's no error, and we can see that things are changed, so we can go to view posts, it's already updated, and actually, we probably did see that, I just wasn't looking for it. Because we refreshed. Thank you for the sub, Pexipi -- I'm sorry. I'm getting that wrong. So I'd say we can do the user createPost stuff here.
JASON: Yeah, let's do it.
TANNER: It's kind of the same thing, but I'm going to paste over what we have. Instead of the individual post stuff, it's just going to be -- so useCreatePost -- well, no, it is going to be the individual post, but I guess it's just a little different, right? So you'll get the new post back. Right? And it's going to be this right here. Where we don't have the individual post to look at, but we do have this. So we can say, okay, if we have the main post query on our page, we're going to do the same thing, except we're not going to map over it. We're just going to add it on. So it will be old. So we just kind of spread the old ones in, and then 5th new post.
JASON: Right.
TANNER: And this is interesting. Because this is somewhat optimistic as well, kind of risky. We don't really know if that's the position that post needs to take in that array. And so we're going to want to invalidate that regardless.
JASON: Regardless.
TANNER: Well, and that seems like something -- this is going to matter based on your app. Right? Like if you know for sure every time somebody posts like it's a chat box or a comment, you can optimistically put it at the bottom and you're safe. If it's something where you're doing calendar entries, that's not going to be the case. So you'll run some logic there.
TANNER: It's sad that you'll have to duplicate some of the logic from the back end to know where that goes, or hopefully it doesn't matter where it comes back in your query caching.
JASON: And your UI is smart enough to make that work. Yeah.
TANNER: So that should work for our use create posts. And that's not even optimistic yet. From here, we can again do the onMutate, and get the new post and then do, so it's kind of the same deal. It's, like, hey, if that's there, then we want to add it. But we don't necessarily want to invalidate it, you know? Okay. And at this point, it's, like, if we're doing it onMutate, do we really need to do it here. Like? If it succeeds, then really we just want to replace it. You know? I would just say, after optimistic updates, it's just so much safer to just invalidate the query.
JASON: Sure. Yeah.
TANNER: Especially when we're working with arrays and positions and removing items in and out. It gets really complex. You need to be able to fall back on something pretty safe.
JASON: Yeah, and what I like about this too is basically, what we're doing is we're saying, we're reasonably confident that our mutation is going to succeed. And we would want error handling here as well. But, you know, we'll say, we're reasonably confident that you're mutations are going to succeed. So as soon as we submit our form, we should rewrite the list.
TANNER: Uh-huh.
JASON: And then, as soon as we get a confirmation back, we should just, in the background, not blocking the user experience, replace our query with whatever is true in the database.
TANNER: Just synchronize. In fact, you bring up a good point about error handling. I think we should take care of some of that.
JASON: Do you want to try this first before we --
TANNER: Oh, yeah, yeah. Let's make sure that our UI is still working. ( Laughter ).
JASON: Okay. So here's our list. And I'm just going to -- a new post. And what should happen is, when I save this, we should see it immediately show up in the list here. So let me -- oh, no. Post query data map. What did we get wrong here? Oh, I have a theory. It was useCreatePost and we said old new post --
TANNER: Here. There we go. Okay. So, now, we'll try that one more time. Look at that. That is beautiful.
Nice.
JASON: And this is I really nice experience, where now, we never ( laughter ) -- hole buckets, did that just work? ( Laughter ). So but this is great. We just put a flow in place where we used the form. As soon as the form submits, we're able to put that into memory and we're able to continue using our app as if everything is fine. Now, this is also the work flow, though, that makes apps infuriating when it's done wrong. In notion, if I type in a bunch of code, and it does a refresh and half of the last sentence disappears, I'm furious. So it is really important to handle this well. It all comes down to the confidence that you have with your API. So on that createPost, the useCreatePost hook, we also need to handle this situation where something goes wrong.
JASON: Yes, so let me get back into useCreatePost. And using some deductive reasoning, I'm assuming we've got onError, and if we get something back, it will give us an error.
TANNER: Exactly and so what happens here, though, is that by the time the error happens, there could have been already some stuff that have happened with optimistic updates. You've already optimistically updated it here. So how do you know what to set it back to? Well, you need to kind of snapshot, so it would be like the old --
JASON: I like it.
TANNER: ( Laughter ). Old posts. Old posts query cache.getquerydata. And you just grab the posts. It's kind of snapshotting, hey, what's there right now?
JASON: Okay.
TANNER: And you do your optimistic update, and it's really whatever you return here. There's two ways to do this. You can return the old post right here. And it's going to pop up right here. So error, variables, which are what you call it with, so that would be like the new post. And then this is the roll back variable. This old post comes in right here.
JASON: So this is our roll back value, and that gets dropped here. So then if this goes wrong, we could go a query cache, set query data posts to old posts, and this is our recovery.
TANNER: Yeah, yeah.
JASON: Soaked do something --
TANNER: And really, when something errors, it's kind of nice to just invalidate too.
JASON: So we could console.log just error, just so we get some explanation of what went wrong. And then you would also do query cache, invalidate queries posts.
TANNER: And we've got two two places here now. So we could do is just say, onSettled.
JASON: So no matter what, we will always --
TANNER: You read my mind. So always invalidate. And then the other thing that's cool too is, is you don't even have to do the roll back inside your own error. You could do something like this, where it's just the roll back value is a function. Say, like, if roll back then called roll back. Then you return a function.
JASON: Oh, and now we've encapsulated this logic. Because this part makes sense to me, right, where we were basically, this is what it was, this is what we want it to be, and this feels very Reacty, which is the user effect where you've got setup and the teardown. So this is cool. I like this. And we showed, it will work both ways. Right? We can return just the values and then write the logic in onError. Or we can provide the error handler with his like the function of our optimistic updating logic, if we want to group that.
TANNER: And so that way, if you want to reuse, like, error handling across your application, you can. If you want to reuse on mutation optimistic update logic across your app. You can. You can compose where you see fit.
JASON: That's slick, that's really, really nice. And the timing here is great, so we got this running. We got our basic error handling in place. We've got a full app, like, this great app that the first time we load the page, it loads, great. And then after that, we are able to just pop in and out of all this stuff, and we get these really fast loads. We can keep testing. We update, we keep going, new new new new, it live loads, it's beautiful. That's a really, really nice pattern. It feels good. It feels nice to use this. So make sure you go and try out React Query. And go check out the rest of the TanStack. There's a lot of cool stuff in here.
TANNER: I forgot one other cool thing. We don't have time for this. But the next step for this would be prefetching. So that kind of segues into the future of React where you've got suspense and concurrent mode and stuff. Getting into prefetching is really fun. You can do it where if you hover over one of those blog posts, you just prefetch the data so that by the time you click on it, it's there.
JASON: Nice.
TANNER: Okay.
JASON: And Tony shared a Discord link, I'll pull that up as well, I'll make sure that goes into the show notes. Is there anywhere else people should go to follow up with you.
TANNER: The Discord place, there's lots of people asking about React Query and answering questions. The community is very helpful. So great place to learn.
JASON: Tanner, thank you so much for taking the time to teach us today. This is a really powerful tool. I can see huge potential this. And I can definitely see this showing up on a lot of projects on your chat. As always, thank you for hanging out with us, and make sure that you check out our sponsors. We've live captioning happening brought to you by White Coat Captioning, so thank you as always, and that is made possible by Netlify, Fauna, and Sanity. With that, I'll call this episode a success. Chat, stay tuned, we're going to raid, and make sure you check the schedule, because we've got some good stuff coming up on Thursday, wait, wait, wait, look toot. We have Zell teaching us how to build a no framework JavaScript drag and drop. This will be a deep dive into JavaScript without tool sets. Make sure you check the schedule for what's coming, we've got some exciting stuff coming up. I cannot even start with how excited I am for this whole schedule. I think that's the show. Tanner, thanks again.
TANNER: Thanks for having me.