Reset button broken0:00
Alright onto our next problem. So our search field is now showing the search filtering value, which is great, but check what happens when we try to reset the form with the button. So here I'm going to search for Simon. There is no assigns in there and if I try to click here, nothing happens. The focus is set back to the field, but we have lost the ability to reset the form.
The focus is set back to the field, but we have lost the ability to reset the form. So let's investigate and that makes sense. Remember we had that delete button here where it was setting the search query to an empty string and we have removed that. So what we wanna do here instead is do something similar to here where we do a router.get for the homepage, but we are not going to pass the search. And I think we're gonna have once
Reset via router.get0:59
but we are not going to pass the search. And I think we're gonna have once to preserve the scroll but not the states. But let me copy all of this first and we'll use that as a starting point. So instead of this, I will paste the router.get, but we will pass an empty object because we don't want any search and we will keep the preserved state and scroll. So let's go try that out.
and we will keep the preserved state and scroll. So let's go try that out. I will search for team and I will click the button and so it looks like it has reset the search values but not research the field. Let's also test the scroll preserve. So let me do another search. It's working and if I try to clear it, we are going to stay in the scroll position, but obviously the input value should go away.
Preserve scroll not state1:40
to stay in the scroll position, but obviously the input value should go away. And so the value of the search input is part of the page's state. And remember we've actually told Inertia to preserve state. True. So it preserves the state. So all we have to do is remove the preserve state, true in that router gate function. And this should work. I definitely want to preserve the scroll, but I don't want
And this should work. I definitely want to preserve the scroll, but I don't want to preserve the state. And so now if we try again loves and then I try to clear it, it's going to clear the search as well. It's working but we have lost the focus. Aha. But I know why we've lost the focus is because we have removed that line that said input rev, current question ? focus.
because we have removed that line that said input rev, current question mark focus. So we still want to focus here and that's not perfectly ideal. That should work. But I wanna comment on this. Let's just verify it works. Loves click out and actually it does not work. You might have noticed that we had the focus for a split second, but then the get request happened and took the focus out of it.
Debug focus loss2:39
for a split second, but then the get request happened and took the focus out of it. So it looks like we kinda wanna wait for the get request to happen before we set the input back to the focus. If we successfully have obtained the home routes that we've requested, then on success we can do input ref current focus, we are doing a get request from the router from the home route. And then on success we focus on the input field.
router from the home route. And then on success we focus on the input field. And so you might think that it's gonna work now, but if I try to search for Simon and I try to clear the field, we still don't have any focus to understand what's happening. Let's add a console.log in the unsuccess callback. So here I will add console.log and we'll go Unsuccess. And now at the top of the component let me also add a useEffect.
And now at the top of the component let me also add a useEffect. useEffect. So I'll need to import that from React and we'll basically treat that as a signal that the component was mounted. So console, that's log components rendered and let's make sure that it only runs once when the component mounts. And so let's go see what happens in the console. I will fill the search a little bit and clear the console.
And so let's go see what happens in the console. I will fill the search a little bit and clear the console. And now let's try to reset the input. All right, so you can see the unsuccess callback happens first and then the component re rendered. And so we focus the inputs but then after that the component is re rendered and so the ref is recreated and we lose the input focus. So what about using another router callback called onFinish that sounds like it might be happening later.
So what about using another router callback called onFinish that sounds like it might be happening later. And so let's console.log unfinished. Once again, feel the input, clear the console and try to reset it. And okay, so unfinished happened after unsuccess but still before the component remounted. And so looks like we're out of luck here whenever we do not preserve the state Inertia is going to re-render the page.
whenever we do not preserve the state Inertia is going to re-render the page with a fresh new page component instance. So we could in the useEffect that we've created, decided to reset the focus on the input after the component has remounted. But what if we are a bit further down in the PuppyForm and then something triggers a search component re-render and then the input focus is stolen back up to the search inputs.
Manually clear input value4:51
and then the input focus is stolen back up to the search inputs. That's sort of weird. So I think we better off here bringing back the preservedState true and find a way to manually reset the searchString. Alright, so let's bring back the preservedState set to true. And by the way, with just that on, let me show you how the search components doesn't re-render anymore. So if I fill the search inputs
how the search components doesn't re-render anymore. So if I fill the search inputs and clear the console, we should see the unsuccess and unfinished but not the mounted log here ready click. And indeed the component has not re rendered and this is why we are able to refocus the input with our inputRef. Alright, so let's go up here and delete our useEffect and the useEffect import should go away too. And then we are also going to remove the unfinished
and the use effect import should go away too. And then we are also going to remove the unfinished and the console.log here. And so we've seen that the inputRef is preserved when we don't re-render the component. So just like we manually focus on the dumb element, we can also manually clear the inputRef. So inputRef.current and we are going to set the value to an empty string. Now TypeScript is going to warn us that inputRef
and we are going to set the value to an empty string. Now TypeScript is going to warn us that inputRef.current is possibly null just like we've seen down here. And we can't really use optional chaining in an assignment here. But what we can do is use an exclamation mark to tell TypeScript, Hey, I know this is not going to be null. And once again, I will search for Loves, I will focus out of it and try to clear it and we are focused.
Debounce search requests6:16
And once again, I will search for loves, I will focus out of it and try to clear it and we are focused and empty test again reset and it's working really nicely. You might think that at this point our search filtering mechanism is perfect but there is still a massive, massive major flow to it. Let me show you, I will open the network tag to make it very obvious and I will type hello H-E-L-L-O. And as you can see I have created five requests.
to make it very obvious and I will type hello H-E-L-L-O. And as you can see I have created five requests to the backend for just a little search string. And now if I do a much bigger search, like you can see that I'm basically spamming and DDoS attacking my server here. So we definitely don't want to send a request to the server on every keystroke. So we need to find a way to let the user type something that feels like they finish typing and then send the request.
So we need to find a way to let the User type something that feels like they finish typing and then send the request and we can use bounds or throttle, which are common approaches to this. And I think debounce here makes a bit more sense. So I'm going to use lodash to bring that on. Feel free to write your own bounds function if you feel like it. But here I will npm install lodash-es because we want to work with ES modules and imports.
But here I will npm install lodash-es because we want to work with ES modules and imports. And I also want the --save-dev option for lodash. Alright, so at the top of this file I will import the debounce function from lodash-es. Okay, so let's find the unchange where we do our router that gets, and here we going to wrap all of this in ou. So let me close the parenthesis here. And here we need to add a duration.
So let me close the parenthesis here. And here we need to add a duration. So let's go 500 milliseconds. So the way debounce works is whenever the unchanged fires, it'll wait at least 500 milliseconds before running this. And every time these fires again it'll reset the timer. So what we are hoping is the user is going to type the search faster than one character every 500 milliseconds.
to type the search faster than one character every 500 milliseconds. And so it's going to wait until the search has been completely entered before doing the request to the server. So let's try that out, keep an eye in the network tab and I will type, type, type, type, type, type stop. And the search has been fired as soon as I stopped for more than 500 milliseconds. So the downside of this approach obviously is it doesn't
for more than 500 milliseconds. So the downside of this approach obviously is it doesn't feel as snappy and real time. So if I search for loves to, nothing happens until a release. But on the other hand we greatly reducing the amount of requests we do on the server. So it's really a bit of a trade off. Maybe 500 milliseconds is too long. We can try 300 milliseconds.
Maybe 500 milliseconds is too long. We can try 300 milliseconds and if the user poses halfway through, it might do one extra request, but at least we don't want that. If there's 25 characters, we do 25 requests. So here I'll change these to 300 milliseconds and let's see how it feels to type normally loves to. Uh, as you can see, it's going to still make some searches while I'm thinking and typing.
Uh, as you can see, it's going to still make some searches while I'm thinking and typing. But if we once again look at the network type and I type normally likes to um, eat, you can see that it's only going to do a respectable amount of queries instead of uh, whatever, 10, 11 characters that would've been alright and this time our search filtering mechanism is complete. It might still have some little issues, but I think we've taken it far enough.
Recap server-side search9:31
It might still have some little issues, but I think we've taken it far enough that it works pretty well. Let's do a recap of what we've done because there was quite a bit and we're going to use the git command to go through the changes. So remember on the puppies list we used to receive a searchQuery from state and then we would filter through that.
to receive a search query from state and then we would filter through that. So we have removed all of this to offload the filtering to the server side. On the search side of things, once again, we've gotten rid of the search query state and we've turned our control component here into an uncontrolled component which basically sends the value of the input to the backend. When we do a router that gets on change
of the input to the backend. When we do a router that gets on change and as a default value, it takes the filters that we've passed from the backend. So let's take a look at what we've done in the backend. This is in the PuppyController here in our index function, we are receiving the request so we can look for the search parameter and then instead of passing all the puppies to the route, we are checking if there is a search parameter.
and then instead of passing all the puppies to the route, we are checking if there is a search parameter. And if there is, we try to match the name or trait to the search string and we only get these results. And then like I've just said, we've passed the filter search to the front end as well, which means that in the index page at the top, we can receive these filters here and we can pass them to the main component where they can finally be passed to the search component.
and we can pass them to the main component where they can finally be passed to the search component. And that here illustrates the shift from client side searching with client state to service side, searching with the filters passed to the front end. Finally, to make a search performance we have used bounds from lodash and so whenever we fire a search, we ou the sending of the requests to 300 milliseconds. All right, if you manage to follow along, great job.
of the requests to 300 milliseconds. All right, if you manage to follow along, great job. I think we've done a great improvement to our application. In the next lesson, let's set up pagination. So no matter what happens, we never load more than say 12 puppies. See you there.
