Simulating Interaction Latency0:00
Up to this point, all our interaction have been super snappy in real time. So whenever I toggle a puppy liked or disliked, it happens immediately. Uh, every interaction that we do is super snappy and it makes sense because we've been using local, state and local data up to this point. So now we're going to introduce some sort of delay in our interactions just to mimic or get closer to a real world situation.
of delay in our interactions just to mimic or get closer to a real world situation where we hit a database, an API somewhere, and then the response comes back with a bit of latency. And this is going to uncover a few technical or visual problems in terms of user experience. And we are going to look at how we can solve these. And we'll start with our simplest interaction, which is the like toggle here. Okay, so let's scroll to the point
Delaying Like Toggle0:46
which is the like toggle here. Okay, so let's scroll to the point where the interaction logic happens. And just to add a delay, I'm going to wrap this in a setTimeout. So we'll put all of the if logic here and let's time out for let's say 1.5 seconds. So now check what happens. I'm going to click on Frisket now one and a half. So it takes a second and a half when I click one.
to click on Frisket now one and a half. So it takes a second and a half when I click one and a half for the interaction to happen. And as you can imagine, it feels unresponsive. You might believe that the thing is broken and the user might click and start rage clicking on that thing. And that's a very real challenge that real applications face because typically you interface with the database that brings a bit of latency.
Pending State Indicator1:25
because typically you interface with the database that brings a bit of latency. So what we want to do is bring a little indicator that something is happening in the background, okay? And one way we could do this in our toggle function is have a piece of state constant pending set pending equals useState. And we'll set that to false as a default value. And so now what I can do in the onclick event listener is instantly when we click the button set pending to true
And so now what I can do in the onClick event listener is instantly when we click the button set pending to true and then inside the setTimeout that kinda represents the database call after the if ls, no matter what happens, we are going to set the pending state back to false. Alright, so next to our heart, let's have a good old pre tag that JS O that string the value of pending. So pending is false and when I click on it, it's true.
that string the value of pending. So pending is false and when I click on it, it's true. And then back to false. true, back to false. Perfect. So let's handle this in a nicer way. I can't remember if I've already showed you how to conditionally display a certain block of JSX, but we are going to use a ternary operator here. So instead of the pre tag, I'm going to check if pending and if yes question mark we are going to display something else.
and if yes question mark we are going to display something else and if no, we are going to have our heart. So it's actually very similar to what we've done here for the classes, yes, no, but here we do it with blocks of JSX and as you can see they're wrapped in parentheses and so that's the syntax for it. So instead of something else, we are going to have another Lucid icon which is called loaderCircle.
So instead of something else, we are going to have another lucid icon which is called loaderCircle. And let's give it a class name of animate-spin so that it spins and looks like a loader and we'll also give it a stroke color of Slate 300. So if the status is pending, we are going to have this loading circle. If it's not, we're going to have a heart that looks either liked or not liked. Alright, let's check it out.
Pending vs Optimistic UI3:20
that looks either liked or not liked. Alright, let's check it out. Let's crawl down and try to love puppy. And we have this spinning icon and then the value changes. Very cool. That's a much nicer experience. This is sometimes called pending UI and it's a pretty common pattern. Another approach that you can do is called optimistic ui. With optimistic ui, the visual state is changed immediately whenever the user interacts with the element.
With optimistic ui, the visual state is changed immediately whenever the user interacts with the element. And so we change to what we expect the value to be and then we sense the database call or whatever you want to do in the background. And when that comes back, if it confirms what we were hoping for, we don't have anything to do and if something went wrong, we sort of revert roll back and tell the user, Hey, something went wrong, I'm not going to cover this here, but React has a useOptimistic hook.
and tell the user, Hey, something went wrong, I'm not going to cover this here, but React has a useOptimistic hook. I recommend you check out that page. Uh, that gives you a primitive to be able to make the instant change optimistically and then do your operation asynchronously and when it comes back you can revert the value if needed. Alright, so now let's look at implementing some pending state for our form right now it's a little bit silly because if I submit the form super quick, it's going
Form Pending with useFormStatus4:22
state for our form right now it's a little bit silly because if I submit the form super quick, it's going to blast images instantly even if I don't feel any data and that feels kinda fake. Oh I've reached the 22 images limit. So here's our form action where we create a new Puppy based on the form data and then update the Puppy's list. And so we could do exactly the same approach here, have a pending state that we set to true
And so we could do exactly the same approach here, have a pending state that we set to true and then we add some delay and then we set it back to false. But what I wanna show you here is that with forms React is going to give us a useFormStatus hook that is going to give us such a pending state and a few other things. There is one gotcha with the useFormStatus hook is that it needs to leave in a child component of the form element.
Status hook is that it needs to leave in a child component of the form element. So what I'm going to do is take the submit button here, I'm gonna copy the code and replace it with a SubmitButton component that doesn't exist yet but we are going to create it down here function submitButton and it's going to return uh, the button markup that we have. So we haven't changed anything, we've just moved uh the button to its own component.
So we haven't changed anything, we've just moved uh the button to its own component. But now in here what I can do is const, gimme some things from the useFormStatus hook. These things that we can get are actionData, method, and pending. So actually let's grab the whole status object like this. And here we're going to change the text of the button based on the pending status.
And here we're going to change the text of the button based on the pending status. So if status.pending question mark Yep, exactly like this, the text will say adding puppy. Do do, do otherwise add puppy. Alright, and now we need to go and update the form action to add some delay so we can actually notice the pending little state for a little bit. So I'll scroll back up
little state for a little bit. So I'll scroll back up and instead of a setTimeout this time I'll turn the action to an async function and here literally add the equivalent of what you would do in Laravel with sleep 1.5. We're going to have some codes that stalls the execution for 1.5 seconds. We can do this in JavaScript with a await new Promise which takes a resolve.
We can do this in JavaScript with a new Promise which takes a resolve. And then we are going to have a setTimeout one more time and call the resolve after one and a half seconds and then we are going to go on with the execution of the function. Okay, so let's try this Tea Rex. Cool bunny because I know a bunny is about to come and so watch the text as I click adding puppy and now it's back to add puppy.
and so watch the text as I click adding puppy and now it's back to add puppy. And so as much as I love this bunny, I don't think it's fair that only the bunny shows up as we are learning things with this form. So I'm going to make a silly change in the image path instead of always having the ID that's coming next, which is seven. I'm going to have math.random() times 22 and then I'm going to bring this down to the floor integer.
I'm going to have math.random that random times 22 and then I'm going to bring this down to the lower integer with math.floor and I'll wrap this. So this essentially gives me a value between zero and 21, but I don't want zero and I want 22 so I'll go plus one. But if you think about it, that's still not completely fair because the first six images have been showing all along since we introduced the UI. So what I really want to do is do plus seven to skip the first six.
So what I really want to do is do plus seven to skip the first six. And so here I need to go 16. So what I'm hoping is this gets a value between seven and 22. And so every time we create a new puppy we have a true surprise of a different image. Very cool. Let's try one more. Alright, it works. Let's quickly add some client side validation. We'll make both the name and the trade required.
Improving Submit Button UX7:52
Let's quickly add some client side validation. We'll make both the name and the trade required. Uh, you should always of course validate this on the server, but here at least it's going to force us to have a name. We are not going to be able to submit without a name and a trade, which means that our cards will always have a nice little UI that is complete. Okay, so our submit button is pretty useful already. What else can we do to make it better?
Okay, so our submit button is pretty useful already. What else can we do to make it better? So if I create another one dusty fluffy and this time I'm going to click multiple times on the button, click, click, click, click, click, click, click. Uh oh. Looks like we've broken our up with some sort of weird unwanted state. So one thing we can do to easily fix this is while the form is submitting and pending, we can set the disabled state yes to status.
to easily fix this is while the form is submitting and pending, we can set the disabled states yes to status that pending and just so we can see it clearer, let's add some disabled stars in Tailwind will go bg-slate-200. There should probably be enough. Oh, and let's add disabled cursor. Not allowed. Okay, so now when I submit the form flex jumpy, the button should go light gray and disabled and I cannot click on it.
the button should go light gray and disabled and I cannot click on it. So I've tried to click many times but nothing happens. We could stop right there. But I wanna show you one more cool trick that you can do with the useFormStatus hook. I'll quickly wrap the button here in a fragment so that I can add an element, a pre tag. You've seen me do that before Jason. That's stringStatus, null two, just so we have some visibility on what's available in that hook.
That's string status, null two, just so we have some visibility on what's available in that hook. And I want to focus on this data object here. So Rexi lovely and while I submit, you can see we have this empty data. So it actually looked like an empty object, but this was the representation of the form data. Check this out. Instead of printing the whole status object, let's try to do status.data.getName() to get the name field.
let's try to do status that data that get name to get the name field. So I believe this is going to error out because status that data can be null and yep, indeed it exploded in our face, uh, for the exact reason that we're trying to do get on something null. So we can probably get away with optional chaining here. So if there is status that data ends, there is a get name, let's try to get that.
So if there is status that data ends, there is a getName, let's try to get that. So now it should work, but we should output nothing because there is no data. But look, if I put Simon friendly when I submit here, I believe we should see Simon. As you can see the useFormStatus hook lets you access the data that is being submitted in the form. And so we can do some pretty cool things with it. For example, instead of displaying adding puppy do.in the
And so we can do some pretty cool things with it. For example, instead of displaying adding puppy in the submit button while it's submitting, why don't we add the name of the puppy that's being submitted? That would be pretty cool. Okay, so I will grab the status data that gets name here, and I will turn this first string into a template tag so that I can replace this with some JavaScript expression oop t status.
that I can replace this with some JavaScript expression oop status. But remember that status, that data can be null so we want some sort of fallback. If there is no puppyName, let's replace it with uh, let's say puppy. Technically when the status is pending, because we have made our name fields required, we should always have a name field. But just in case, let's display puppy in case
we should always have a name field. But just in case, let's display puppy in case that the name is not found. So I'll get rid of the pret tag and I can remove the fragment as well. And let's go try it out. Let's make a puppy called Jeffrey this time. Uh, he's very funny and so watch this adding Jeffrey. Boom. Ah, which one is he? All right. Pretty cool stuff. The useForm status hook is very useful when you wanna
Ah, which one is he? All right. Pretty cool stuff. The useFormStatus hook is very useful when you wanna provide some pending UI, uh, and also access the data like we've just done. One thing that's very important to remember is you need to have this useFormStatus hook called from a child component of where the form element is. So you need to break out the submit button into its own component and then from there call the useFormStatus hook.
component and then from there called the useFormStatus hook. Otherwise, it's not gonna work. We've successfully added a real fake delay to our interactions and we also using real fake data from a local file. That said, in the next lesson we're going to fetch external data into our app and see what that looks like in React.
and see what that looks like in React.
