Fixing Auth Header Bug0:05
In the previous lesson, we converted our project to next and we introduced SSR rendering. While sharing our state between the server and the client, I noticed a small mistake where we lose our authentication header. Let me quickly show you in our User global middleware, we say user store in it, then we say set token with a token. And in here we call fetch User. Now this is where the mistake is. We await the result of user.get. I remember because we set the authorization header.
We await the result of user.get. I remember because we set the authorization header as a Monday default, it should be accessible. But as you can see here, we only set the defaults afterwards. And in order to make this call work, we actually have to pass in our authorization header manually using the AU token we stored using the pinup persisted state plugin. And after we do this, our entire authentication mechanism will work again.
Introducing Async State0:53
And after we do this, our entire authentication mechanism will work again. Remember, the art token gets stored in a cookie using the Pinia persisted state plugin and this is why it's available in the user store. Alright, now let's get started with our lesson about Async State. I provided a new API endpoint that we can call to get an estimated delivery time of our food. And if we execute the catch request,
Displaying Delivery Time1:13
to get an estimated delivery time of our food. And if we execute the catch request, we can see we get a timestamp back and an ISO formatted date. The estimation is based on the current time. We have a preparation of 15 minutes and we will then round it up to the next 15 minutes. Let's open up our index and above the product we can add an alert. And in here we will display our estimated delivery time.
and above the product we can add an alert. And in here we will display our estimated delivery time. Let's for now just say estimated delivery time 12. And as you can see on our front end, this looks a little something like this. Now let's create a new Mandate layer for fetching our delivery time from our backend. We'll say deliveryTime equals Monday. And let's say deliveryTime, let's import Monday. There we go next.
And let's say deliveryTime, let's import Monday. There we go next. And we only want to execute this API call on the client. So we can say if process.client, let's create an anonymous function. Let's async and let's call it directly in here. We'll do our API call. We'll say const response equals await deliveryTime.get() and let's log it to the console. And when we take a look at our console,
Get and let's log it to the console. And when we take a look at our console, we can see we get a response back as expected. We can also take a look at the network tab and we can see that the API call gets fired correctly. Next, let's format the timestamp and display it in our info alert. We'll say const date equals new Date(). We'll take the timestamp and we have to multiply it by a thousand.
We'll take the timestamp and we have to multiply it by a thousand. And then we will say, const time equals date.toLocaleTimeString(). We'll pass a non-empty area. And our configuration looks at something like this. We need two digits for the hours and for the minutes, let's create a state variable. const time equals ref(''). And let's say timeValue equals this. And instead of the hardcoded value,
And let's say time value equals this. And instead of the hardcoded value, we will say time in our template. And this almost looks correct, I think I made a mistake here. It's gonna be hours instead of hours and now everything is correct. While we are refreshing, however, this looks a bit dirty because it's gonna be empty for a while. So let's add a thing called a skeleton from sheen.
because it's gonna be empty for a while. So let's add a thing called a Skeleton from sheen. We can say MPX sheen view at latest at Skeleton and evolve. Well, we now have our Skeleton component, which we can use in our application. Let's import it. Let's say import Skeleton from components/ui/Skeleton. And in our template, let's display the Skeleton as follows. Skeleton, let's give it a class with 30px in line block a height of 4 and maybe rounded-full.
Skeleton, let's give it a class with 30px in inline-block, a height of 4, and maybe rounded-full. And we only want to display this if we don't have a time. So if $loadedTime equals false, which we still have to create. $consLoadedTime equals false. Let's make some space here. Let's wrap this in a span and let's say span v-if, else, we will add our time. And maybe let's make this bold from bold cover this in the div and let's give it some flex styling.
And maybe let's make this bolt from bolt cover this in the diff and let's give it some flex styling. There we go. So if we go back to our browser, we can see our skeleton being rendered great. After our API call completed, we can say load the time value equals true. So when we refresh we will first see our skeleton and then it'll change to the estimated delivery time. Let's actually go to our backend and maybe like sleep for a second.
Retrying Failed Requests4:47
Let's actually go to our backend and maybe like sleep for a second so we can see it more clearly. And now when we refresh we will see the skeleton for a second before the time appears. Now while the backend is open, let's do something naughty. Let's add a little chaos into the mix and we can do so by checking. If a random number between 0 and 10 is lesser than
If a random number between zero and 10 is lesser than or equal to eight, we will throw an Exception. Now when we refresh, we'll have a very high chance of the API call crashing. So let's see how we can mitigate this in the front end by adding a retry mechanism. So let's cut our anonymous function here and let's say try to load deliveryTime and let's create that function.
and let's say try to load delivery time and let's create that function. It's gonna be an async function. And there we go. Let's take two parameters. We can say attempt equals 3 and currentAttempt equals 1. Let's wrap this in a try catch. And if this fails and the currentAttempt is lesser than the total amount of attempts, we will retry and
and the current attempt is lesser than the total amount of attempts, we will retry and otherwise we will just have like an error. And let's update the value of time to error and let's say load the time do value equals true. So let's attempt to retry the loading of the delivery time and let's do so with a simple back off strategy. We can do so by saying setTimeout. We will wait for a second multiplied by the current attempt. So the first time we'll wait for one second, then we'll wait
We will wait for a second multiplied by the current attempt. So the first time we'll wait for one second, then we'll wait for two seconds, three seconds and so on. And in here we will call, try to load deliveryTime with the amount of attempts and the current attempt plus one. Now when we refresh the page, we will see our API call. This time it succeeded in the first time, so let's refresh again. So we will probably see it fail right now.
so let's refresh again. So we will probably see it fail right now. We'll see it retry, we'll see it fail, we'll see it retry and this time it worked and we will update our time. Great. Now this is a pretty solid mechanism for fetching the state with a nice backup strategy. Let's also add a re fetching mechanism every time we revisit the page because the data might be stale. Now this code will only have to be executed on the client as well.
Now this code will only have to be executed on the client as well. So we can say window that at event listener we can say focus, we'll say handleFocus, focus and try to load deliveryTime. Very important. You also have to do on beforeMount and we also have to remove the event listener. There we go. So now when we go away from our page and we return, you'll see
There we go. So now when we go away from our page and we return, you'll see that the delivery time API call gets re executed. So we can try that again. We can go away from our page, we can go back and we can see that the API calls get re executed so we can guarantee that the estimated delivery time will be up to date. Now as you can see, this is a whole lot of states to manually manage. Let me show you a better way.
Refactoring with TanStack Query7:56
of states to manually manage. Let me show you a better way. Using 10StackQuery, the first thing we do is add the dependency. And because we're using Next, we need to create a plugin which we can get from the docs. So we just have to copy this plugin, go to plugins and we'll call this ViewQuery.ts and we'll paste it in. Now in this plugin we initialize the library
and we'll paste it in. Now in this plugin we initialize the library and set some sensible defaults like a stale time of five seconds, but more on that later. Let's jump back to our index and refactor our data fetching to 10StackQuery. Let's comment out everything we did except for the mandate layer. The first thing we'll do is import useQuery from 10StackViewQuery and then we'll say useQuery.
The first thing we'll do is import useQuery from @tanstack/react-query and then we'll say useQuery. Every query needs to have a queryKey. So let's say queryKey is gonna be deliveryTime. Now this is used internally by tanstackQuery to identify the query. Next up we'll say a queryFn. That's gonna be deliveryTime.get. And we'll pass in an enabled flag and we will only execute this on the client.
And we'll pause an an enabled flag and we will only execute this on the client. So we can say processClient. Alright, now let's capture the return value of useQuery. Using a D structured object, we can say const, this error is fetching, is pending, and data. Next up, let's create a computed property. We'll say const, computedTime equals computed. And in here we can check if we have an error,
We'll say const, computed time equals computed. And in here we can check if we have an error, we'll say return error. And otherwise if we have data we'll transform our date and we can actually just copy this from here we'll say const date equals new Date(data.value.timestamp), and then we have to format the date and return it as we did before. Now if we go to our template, we have to change a few things because here we have loaded time and time.
Now if we go to our template, we have to change a few things because here we have loaded time and time. This one will be computer time and loaded time will have to change to isPending or isFetching. And now when we refresh, everything still works like before and we can see this time the API call return to 200 and we can see our estimated delivery time update. Now let's refresh again. It'll probably fail.
Now let's refresh again. It'll probably fail and we can see that Vue Query has a built-in retry mechanism and this is really awesome. By default, it'll retry four times before giving up and you can configure this any way you want. When we refocus the window after five seconds, we'll see the data gets re-fetched because we set this up in our plugin, the data is still after five seconds, so Vue Query will re-fetch all the data.
because we set this up in our plugin, the data is still after five seconds, so view Query will re fetch all the data for us automatically. So we can try that again. We can go away, we can come back after five seconds and we'll see view Query will retry to fetch the delivery time for us. Now this is pretty great. I'll change the chaos factor to about 50% instead of 80%. That will make it a bit easier to see.
Abstracting into Reusable UI10:59
I'll change the chaos factor to about 50% instead of 80%. That will make it a bit easier to see. Alright, now let's abstract this away in its own composable and we can also add a time indication on our checkout page. Let's open up our project. We'll say composable, we'll create a new file. We'll say useDeliveryTime.js. We can copy this entire block and in here we'll say export function.
We can copy this entire block and in here we'll say export function. Use deliveryTime. Let's paste our imports at the top and we'll return our computedTime or isError is fetching, isPending. And data. Now back in our index, let's get rid of our comments. We can say const, computedTime, isPending and isFetching equals a
We can say cons, computed time is spending and is fetching equals a use delivery time. Don't forget to import it. And now when we go back to our browser, we'll see everything works as before except now it's abstracted away in its own composable. Now let's create a component of this text indication. We'll say estimatedDeliveryTime.
Now let's create a component of this text indication. We'll say estimated delivery time. Let's create a component and we can paste it here back in our index. We can get rid of the skeleton and we can actually cut our composable from here and paste it here. Everything should still work except this is now a component we can reuse across pages. Let's add something to our cart.
we can reuse across pages. Let's add something to our cart. Let's go to the checkout page and down here below confirm order, let's add an indication of banner order will be delivered. We can open checkout and below the button we'll say maybe a diff estimated delivery time and maybe say Flex and JustifyCenter and let's add some margin to make it a bit cleaner.
and Justify Center and let's add some margin to make it a bit cleaner. And now we'll see our component will render below the button as well. Now when we go up and we go back home, we'll see that View query will revalidate the delivery time because we're outside of the five second stale window. But if you go to our plugin and let's increase the stale time to 30 seconds, refresh the page, we see it succeeded.
and let's increase the stale time to 30 seconds, refresh the page, we see it succeeded and now we go to the checkout page. We'll see that View Query will not fire a new API request because the data is still considered fresh. Now when we go back, we're still in that 32nd window and we still won't see a new API request. But now I assume 30 seconds have passed. Have we pressed Checkout? We'll see that View Query will revalidate the Time Tent.
Have we pressed Checkout? We'll see that ViewQuery will revalidate the TimeTent. StackQuery, formerly known as ViewQuery, is a very powerful library that allows us to manage asynchronous state without too much overhead and it takes care of caching and invalidation out of the box. For us, tentStackQuery is my go-to library whenever I need to manage this type of async state. This is not necessarily a replacement for Pina.
whenever I need to manage this type of async state. This is not necessarily a replacement for Pina as Pina still has a lot of benefits for managing client side state that doesn't live on the server. I would definitely recommend you take a look at the 10Stack Query docs as there are a whole lot of options that make your life as a developer easier. For example, doing paginated or infinite queries, pausing queries,
For example, doing paginated or infinite queries, pausing queries, invalidating queries, and much more. Let's move on to the next and final lesson where we'll go over everything we covered in this course. I'll see you there.
