React Query: It’s Time to Break up with your "Global State”! –Tanner Linsley

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
[Music] [Music] [Music] hi everyone my name is Tanner Linsley and I'm a co-founder and VP of UI and UX at nozzle IO where we build SEO rank tracking software for enterprise I absolutely love react and JavaScript and I have a bit of an obsession for building open-source software as well so since learning react I've been super obsessed with topics like static site generation data visualization and animation but today I'd like to talk to you about what is possibly my favorite one of all global state management today a ton of code in our applications is dedicated to consuming and manipulating asynchronous data whether that data comes from our users or servers or a third party API s it's absolutely critical for our apps to provide value to our users in fact for a lot of us our applications are just opinionated user interfaces for consuming and managing this data over the years I've noticed that patterns around accessing and manipulating our data in our applications have quickly taken up residence with what we all know as global state global state is super convenient it helps us avoid prop drilling and it lets us access data across our application without copying or duplicating it and it even helps us communicate between isolated components and hooks that otherwise otherwise wouldn't be able to in the end it just helps us do more with less code it's extremely accessible and powerful so it's only natural that we would want all of our important server-side data to be just as accessible as our global state and with that expectation it's no surprise that we as react developers have chosen to co-locate our server-side data with the rest of our global state it's relatively easy to do this by using something like local component state with react context or even by using any number of libraries from the ever-growing list of global state management tools out there but in the end the expectation is usually the same we expect our global state not only to be able to handle trivial things like menu state themes things like toasts and alerts but we also expected to be responsible for complex life cycles around fetching and providing our server-side and asynchronous data to our application so today I'm here to tell you that despite the fleeting convenience global state gives us when working with server-side data I think we've made a really big mistake by placing it there we've tricked ourselves and our code into thinking that all state is created equal when I think our asynchronous data and global state could not be more different especially when it comes to where they're stored the speed at which we access them and how we access and update them and ultimately who can make changes to them to make all this easier to understand I want to stop using the term global state and instead call these two different types of state client state and server state client state is relatively simple and should be familiar to most developers it's temporary and local and it's generally not persist persisted between sessions it's access to a synchronous API is that don't have any latency and most of it is owned by our clients application instance so for all those reasons we can pretty much rely on our client state always being up-to-date at any given time in our application server state however is pretty different server state is persisted remotely so the location and source of truth for our service state is potentially unknown or at least outside of our control it's asynchronous so we have to access it with asynchronous api's and it also has implied shared ownership which means that it's not just owned by our client it can be read and manipulated by both the server and any other potential clients that interact with it because of all these things very few guarantees can actually be made around our service state always being up to date in our apps and instead we usually end up relying on just snapshots of our async data so when we take these two very diff types of state server state and client state and try and store them in the same system we will eventually make trade-offs that favor one or the other a good example of this is that server state has its own unique challenges that we never faced with client state for example a few of these might be things like caching DD ping requests updating data in the background dealing with outdated requests dealing with mutations pagination and incremental fetching or even garbage collection error memory management and everything else that comes with caching in general many global state patterns don't offer solutions for these kinds of challenges or at the very least attempt to solve them with complicated api's or over engineered plug-in systems and sometimes even overpowered api's that are basically foot guns for the average react developer server state and client state clearly both need plenty of love but they each need it in their own way and while I think they have a few things in common when it comes to how we access them in our apps I think it's time for server state and client state to break up there is way more to server state than just being globally accessible and I think it deserves new dedicated tools to not only solve these challenges but automatically handle them in an elegant way this is exactly why I decided to build react query react query is an NPM library comprised of a couple hooks and utilities that aim to solve asynchronous server state it's a small API it's simple and it's designed to help both novice and advanced react developers be successful while requiring little to zero configuration to really know how react query can drastically transform the way you handle service state I decided to build a small interactive blog application using react and a little API powered by next j/s so the purpose of this application is pretty simple it's to show a list of posts it lets us look at a detailed view of an individual post and then it allows us to edit existing posts or add new ones as well I'm going to navigate through a few stages or commits that I made to this project of how this applications state management evolved in the wild and how in the end I finally got to use react query to save the day so first let's just get familiar with the app we have a trusty sidebar with a single link into our post page we have a post page that fetches all of our posts from our API and displays them in a list we can click on a post and load the detailed view with the full content we can use the Edit form at the bottom to edit the post and then back on our post list we can use that same form to add a new post to the list so to do all this our app started out with four main components an app component which handles all of our routing and routing state a post component which fetches our post from the API and then displays them with the add new post form underneath them an individual post component that fetches the full content for a post and renders it and then it gives us the edit form at the bottom and then finally we have a reusable post form component that just is for editing the post fields so in each of these post components right now we're just using a use effect strategy to call an asynchronous function and fetch our data and then we use react state to keep track of the loading States for those requests this way when we eat when we mount each of those components the data gets requested and eventually gets rendered in our UI and you know all of this is fine and it works but I personally don't like having a lot of business logic in my components so I want to see if we can make this a bit more portable in this next commit I've extracted all the logic for fetching our data and put them into their own custom hooks so the used posts and use post files now contain all of the same logic and state for fetching our posts lists and post detail but just combined into a custom hook and then I've also moved all the logic for our mutations into some new files called use create post and use safe post that work in a similar way so this frees up our components from needing to define all the logic themselves and just focus on rendering the UI which in my opinion is a great pattern regardless of how you manage your state so now that we have our fetching logic inside of these reusable hooks we can use them again anywhere else in our app that we want also in this commit I added a handy little total post account in our sidebar that calls the same used post hook again to show a total count of posts so with all of this abstraction it might seem like a big win for code reuse but if we look at our network tab we're gonna see something weird is happening our post endpoint is being called twice once for the post list and again for the total post counter even though we haven't extracted the state into even though we have extracted the state into reusable hooks it doesn't really mean that the data inside them is itself were usable the real reason this is happening actually is because every time we use our use post hook we're creating a new instance of state and effects for every time that we render it so if we keep this up we're going to be double requesting a ton of data for our app over and over again every time that we use that hook in this next commit I'm going to show you how I try and fix that by turning some of the state and fetching logic in our hooks into global components that only get rendered once for the entire up so I've created a route component for our post list and moved all the fetching logic into that component this way we have a single location where our post list veteran can happen for our entire app as long as we render it at the root and only render at once since we can only render at once we have to take all that state and logic and send it down our react component tree through a context object this way in our use post hook we can subscribe to that context and get access to our global post list for our app so let's go see if that solves our problem no it did not it looks like our post endpoint are still being double requested this might seem weird but it's actually because any we load our used post hook we need to make sure that it's fetching the data so that it's up to date for the component that's going to use it but this also means that every instance is going to be calling the fetch function every time that it mounts even if they mount at the same time so luckily I know of a way to fix this and we needed the global state anyway so at least we didn't waste our time with that in the next commit I'm going to show you the easiest way I know how to do asynchronous requests that are happening at the same time so we start by using a react ref to track the promise from in the outgoing requests then if any other requests that happen while the promise is still pending we can simply reuse the promise from the original request so that we don't fire off any extra ones and each async function can still resolve when the original request comes back and if we go back to our network panel now you can see that there's only a single request for our post endpoint and both our post lists and total post counters finished loading at the same time which is super awesome so up to this point we've been giving our post lists a lot of attention let's switch gears and check on our individual post view it's great that we can click on a post to see its content but I knew eventually that I was going to need a way to look up a post by its ID as well which I can't do with this UI yet so in this next commit I've added a little search box to my sidebar that lets me enter in a post ID and query my API for it we'll just go grab an ID from one of these posts put it in here in the search box if the post is found it shows the title and lets me click it to open the post which is super cool but soon after I added this feature I noticed another weird thing was happening when I would edit a post that I also had loaded in the sidebar so if we edit this post the post detail gets reloaded with the right title but the sidebar stays stuck with the old one and the way our app is structured right now there isn't really a good way to force that sidebar to reload without introducing some more global state it was pretty easy to convert our post lists into global State so I thought it shouldn't be that much harder to do with the individual post state as well and then in this next commit I'll show you a few difficult things that I ran into when you know that I didn't have to worry about in the post list state but how I got around them so what happened here is that our post state our post detail state actually needed to store a separate state for every single different detail that we were requesting from the server including status data error and even its own little promise reference so that we could track and do do promises on a per post detail level it was a bit more work but it came together okay and it works just as well as our post list global state does and you can see now if we grab a post ID go search for it in our sidebar try and update the title both the detail view and the one on the sidebar will each get updated and even share the same request so this is definitely more consistent than what we had before and even though I felt like we I guess up to this point we have accomplished quite a bit with like adding global State and all this stuff there's still a couple of issues at this point that are still bugging me first off I think there are way too many loading States getting shown and honestly after all the hard work we just did with global state it was kind of a disappointment that all of the loading states were still happening another thing is that when we trigger a fetch for one of our posts like with the sidebar search while also looking at the same post in the detail view the detailed view gets put into a loading State too when it doesn't really need to and honestly it feels like a bad user experience and then the other things that we haven't even looked at yet are things like caching pagination and I don't even really want to think about how we could synchronously access our global state right now to do optimistic updates on our mutations so we could just keep going on this insane rabbit hole of trying to bend global state to our will and handle all these edge cases but you can take my word for it that it gets pretty complicated very quickly even for the most advanced global state managers out there it gets pretty crazy and with that said I finally he gets time for react query to swoop in and save the day so in this next commit I've moved all of our data fetching hooks to use react query instead of our global state and use effect logic and as you can see we were able to blow away about a hundred and fifty lines of code from our posts hooks and our post hooks as well and replace them with a few lines of react queries used mutation oak oh no use query hook all we had to do was get a unique key for our data and the asynchronous function to go fetch it so let's check out how that works all right it's definitely working and honestly it feels a lot faster as we move around the app especially for views and posts that we've already visited and this is mainly because react query automatically handles caching and backgrounds reef etching right out of the box and when things are cached they can be rendered immediately next time they're viewed and honestly it can feel a bit unreal at times like when we search for a post in our sidebar and click the link since the post is already cached from the sidebar look up the detail view loads instantly and you'll even notice a little upstate updating status at the top telling us that it was being fetched and reloaded in background even though it was reloaded immediately so this is all super great data fetching is working way better and we got to delete a ton of code I want to check out now how our mutations are working and how they feel so adding a post is way better now because it doesn't go into the loading state to fetch the new post and it doesn't even well and it just gets background refreshed and added to the end of the list so we don't get that jarring loading state that we had before and same with editing a post we won't see that loading State anymore either you'll just see a background update happen and a new title will appear so even with this new button I added to delete a post it even takes us back to the post page without showing us a loading state and just triggers a background update which is great I mean honestly all this is really awesome considering that right now our mutations are still really naive in fact when we fire off our mutations in our UI components right now we are still having to make sure that we call the refetch function for any queries that we need to update in the background and while that does work there is a much easier way to do that that I'm going to show you in this next commit and in this next so in this next commit I migrate all of our mutation hooks to react query as well and the way we do that is with the used mutation hook and the used mutation hook helps us remove again helps us remove a ton of code that was dedicated to handling our mutation state and just replaces it with a single call to use mutation and then we can pass in our asynchronous mutation function to that and even if that's all we did was just get rid of a bunch of code I would be happy with that but use mutation has a couple more options that help us do way more the most obvious one you can see here is onsuccess which is just a function that i've passed that gets called any time that our mutation succeeds so inside of that callback we can use react queries other import called the query cache to notify any related queries that they need to be refreshed and since we are doing that there all of the reef fetching logic in our opponents is now gone and we can just declaratively define our mutation dependencies one at a time inside of our mutation instead of having to manually call all the queries to refetch every single time that we run a mutation in our UI components and so up to this point these things haven't really done anything different for our UI other than just making the code more maintainable and at the end of the day we're still dealing with a server that takes a second or two to apply our changes and give back to us that we need to refit to me and probably all of you as well those few seconds can feel like an eternity and so I say what if we can predict the future what if we could set up our state so that it's going to show what we expect it to show after the server is done but just do it immediately in this next commit I'm quickly going to show you how to use use mutation to do something called optimistic updates they're a really great way to make your you I feel really fast like you're not even working with asynchronous data and use mutation has a couple options so we can pass to it like the on mutation callback which runs before our mutation function gets executed the on error callback which gets calls when our mutation function fails and if we combine all of those with react queries query cache and its ability to read and write data from my cache synchronously we can optimistically update our UI and then in the case that the the mutation fails we can just roll back to a previous value and if it succeeds we can trigger a background refetch to make sure that we're seeing the actual server state instead of just the best guess so if you watch closely as I use this ad post form here you'll see it do exactly that there's a 50/50 chance it will fail so sometimes it succeeds and sometimes it fails but you can see our UI magically rolls back or updates whenever each scenario happens so our app is now awesome it feels great it's very fast and it's really maintainable too but all of you saw that when we started this app it was quick and familiar to jump between those common UI patterns that we lean on when we deal with a global state and even though we can put a lot of hard work into those global state patterns a lot of things can get out of control pretty quick so because of that I believe that tools like react query are the future for handling all right our asynchronous data not only do they solve the challenges with server state but it also helps us model and global model our global State and think about our global state with a new perspective and in some cases it even removes all of it or if not most of it from our application I've gotten a lot of feedback from people who've used react query and people have great things to say about it I'd like to share a few quick fun ones with you really quick my friend Kent C Dodds said that react query is a missing piece to react application development that I've been looking for after years of building react apps finally I have a tool that gives me exactly what I need to solve my application state management problems without giving me more problems it's fantastic Marcelo Alvis said I still really like Redux but anytime I remove a piece of the store and replace it with react query it's a huge win and then Demetrius Clark said finding react query has helped me maintain an extremely productive workflow inside of react with queries and mutations my components express intention clearly and I can finally tune the UI to a flow my users expect due to the powerful caching strategies react query is now my go to server state manager so coming full circle here I'd like to leave everybody with an invitation to go and look at your global state for your applications and take note of just how much of it is server state and how much of it is client state I think you're going to be surprised at how much of it is actually outside of your applications control and that by opting to handle your server state with a tool like react query you'll again be surprised at how little client state you're actually left with and how much better your apps user experience will be so thanks for listening and be sure to follow me on Twitter YouTube and github and don't forget to check out all my other open source projects and especially my startup nozzle dot IO thanks I got a safe dinner that was impressive thanks a lot for this great talk I make sure the board is rocking with react query at my own object I also they don't think you charge ready our rights [Laughter] you shouldn't do these things if you charge by the hour come on you're killing us you're killing us I want to go jump right into the audience questions we have a few and I'm going to start with the first one is react weary also spirit in graph QL by using Apollo client so react query can support graph QL but it doesn't do it through Apollo if you want to use like a simple graph QL client to fetch data really you can use whatever you want to fetch your data as long as it returns a promise and your data so I actually have a lot of people who are using just a simple graph QL client and react query together and they say that they love it so there's a lot of awesome stuff to happen there but cool sounds good the next question is kind of a play of that it's is there something like Apollo but for a more generic way of communicating with the server for dengraf though yeah that is a good way to put it it's it's more generic it's not built specifically for graph QL or really any type of specific data layer or protocol as long as it's transactional based and users promise it returns it'll work it just won't promise us yeah I don't I need I need your promises all right cool and then we have another question from Ali he asks for the little remaining client state which you personally use Redux for context or what's your go-to way of doing this yeah actually I had such little state left over after I moved everything to react query that I ended up just moving it to some context I use a use reducer and one or two base level components that provide that state through context to the rest of my app and honestly I haven't been happier there's such little state left over that it's super easy to manage on your own okay oh and also that was a good question too I also answer about Redux I have a lot of people who have switched over to using react query for their data fetching and still use Redux to manage their true application state so they do work well together so they can live by side by side in harmony mm-hmm okay question from well my I don't know what the correct complete names recently these conference ID /sw are and it looks great first time seeing reactor in action in Italy great too they seem to be accomplish similar things if you know could you provide some comparisons and opinion on use cases that may be suitable for one versus the other or are they pretty much comparing apples and they are very similar in terms of what they're trying to accomplish the api's are different just because they weren't developed in tandem we each have our own ideas about designing api's and i actually have a list of like the very like minor differences and some really important differences in the react where you read me there's a little drop down there that says how is this different from Zeit and you can go and look at some of the things there that are different one of the main differences i think is how queries are cached by keys the key structure free act query is a little different and in my opinion a little more flexible so that you can select which queries you want to research and whatnot and also the entire concept behind use mutation i think is a really useful hook especially when you start doing things like optimistic updates and wanting to declaratively handle side effects to your mutations and that's something where that concept doesn't really exist in SWR right now so that's one of the biggest reasons why I like react query well you're prejudiced I would say yes I am extremely biased all right tanner that's all the time we have for questions right now if you have any questions left for Tanner you can go to resume room Jenna thanks a lot
Info
Channel: React Conferences by GitNation
Views: 92,834
Rating: undefined out of 5
Keywords: jsconf, remote conference, online webinar, react rally, react, react conference, javascript conference, reasonml, javascript, react amsterdam, react conf, graphql, development, js conf, react amsterdam 2020, ecmascript, react europe, js, react native, javascript conf, reactjs, online conference, web-development, react summit, react europe conference
Id: seU46c6Jz7E
Channel Id: undefined
Length: 29min 1sec (1741 seconds)
Published: Mon May 18 2020
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.