In this video, I'm going to show you how to correctly use the URL query parameters as state in React, because sometimes you want your state to be persistent. You want your state to persist perhaps when you're reloading the page, or even if you're sending the URL to somebody else for them to see, you want to also send along the state with it. Now, before we get into that, there's two things that I want to mention.
And the first one is that if you want to learn how to build a real project with React, a project that's big and complex and has a lot of moving parts, definitely check out Project React in the description. I promise you, you will not regret it. The second thing is that I've also recently launched a newsletter that's all about React.
It's called Import React. And honestly, I love the name, it's completely free. And you get it delivered to your inbox every single week, you're also going to be able to find it in the description.
Cool. With those things being mentioned, let's now look and see how we can actually put things and use the URL as state in React. Alright, cool, let's begin.
So this is the application that we have and that we're going to be working with in this video. This is actually the same application that I did in the video where I showed how to create a custom filter component, which is this component over here, the search the category and the max price, right? So we can actually search or we can do something like product, this is going to work and it's going to search.
But now the problem is if I refresh this, we lost that state, this text input here has no value, we lost that state. Even worse, if we tried to send this URL to somebody and we wanted to also send our search query, this will be lost because nothing is stored in the URL. So in this video, we're going to fix this is the code that's running this application, I will briefly go over it because I did build this in a previous video.
But essentially, we have the app component over here. And we have a couple of state variables here, we have one state for the search one for the category and one for the max price, right? Simple enough.
Then we're taking those values, and we're passing all of them to react query, we're passing them here in the query key so that we have our caching working as we expect. And we're also passing them here to the fetch products function. And the fetch products function is going to take those values and return to us the products that actually match.
And then over here, we have the JSX that's actually running this entire application, it is pretty simple. And we have the product list filters component, which takes in an onChange function, it calls it with the filters. And here, we're just taking those filters and putting them in the state of this app component.
And then the product list filters component has the same state variables over here, it receives the onChange function. And we basically have a use effect over here, whenever any of these values changes, we're actually changing and calling the onChange function with these values. And then we have some JSX over here that just has everything that we need to actually render the UI for this filter component.
So now what we're going to do is we're going to take this code, refactor it, put all of this code inside of the URL instead and make this hopefully a little bit better. But before we do that, I just want to take a very quick moment to talk about brilliant, which is the sponsor of this week's video. So brilliant, just like what we're doing here in this video is a place where you learn by doing you have 1000s of interactive lessons in various topics such as math, data analytics, AI, and more importantly, for us programming brilliant has a platform that is designed to be uniquely effective.
Each lesson that you have on there, you get to solve using a hands on approach that lets you play and get a feel for all the different concepts, which actually has been proven to be a method six times more effective than watching traditional lecture videos. Plus, all of the content on brilliant is crafted by an award winning team of researchers and professionals from places such as MIT, Microsoft, Google, and more brilliant actually helps you build your critical thinking skills through problem solving instead of memorizing. So while you're building real knowledge on specific topics, you're also learning how to become a better thinker.
And you can do all of that on brilliant with literally just a couple of minutes a day, you have fun lessons that you can do whenever you have the time. It's the opposite of mindless scrolling. And it actually builds a great habit.
And if you want a recommendation for a great course, check out thinking in code, which follows what we're doing in this video and teaches you solid foundations for computer science, which is going to directly help you as a react developer. To try everything that brilliant has to offer for free for a full 30 days, visit brilliant. org slash cause and solutions or click the link in the description.
You're also going to get 20% off an annual premium subscription. Thank you once again to brilliant for sponsoring this video. And now we can get back and start refactoring and start using the URL as state and react.
So to be able to do this in react and to put everything in the URL efficiently and have this work really beautifully, we're going to need to use a library that is all about routing. In this example, we're using if I just open up the main. tsx, we are using react water, which is a standard, it's a very popular library for routing in react applications.
So here we're creating our router, and then we're doing all of the traditional setup codes to actually put everything through a router. And this is going to give us access to some handy hooks that allows us to modify the URL, which we can use to actually put things in there. Now the first thing that we're going to want to do is we're going to want to take all of this state and have this come from the URL instead of just be simple state.
And the second thing that we want to do is we actually want to get rid of duplicate state. Because we have this state here, we have the search category and max price. But we also have it here in the app component, literally the same state variables.
And we have this unnecessary on change function, which honestly, we don't need to this code isn't as efficient, even though I wrote it, right, that's okay. That's part of being a developer, we're going to now improve this and make this a little bit better. And the best way to do that is actually to create a custom hook and then put all of these state variables in here and then create a function to update them and then use that hook use that custom hook in both places or in any place that we need to have the same filters for the products.
So let's do that. So I'm going to come here in the sidebar, I'm going to go inside of my hooks folder, and I'm going to create a new file. And we're going to call this one use product filters dot ts.
In here, I'm going to define a function export function use product filters. And we have the function over here. Now in here, because we are using React router, right, that's the reason why we're using this, we're going to use one hook that React router provides us, which is going to allow us to easily access the search parameters of the URL.
And that is going to be the use search params hook. So we're going to do const search arms, set search arms, and that is going to be equal to use search params. And we're going to import this from React router.
This has a very similar syntax to use effect, this is the actual value of the search parameters. And this is the function that we can use to actually set any search parameter value, right, it's pretty simple, it's straightforward. And we get access to this because we are using React router, then what we want to do is we want to get and create variables for each of our state properties, right.
So that is going to be the search the category and the max price. So we're going to come here and make a new line. And we're going to have those come from the search parameters.
So we're going to do const search equals search params dot get, which is a function that we can call, then in here, we're going to pass search like this, right, this is going to be our key, which is going to be stored in the URL. And then what we want to do is we actually want to get the correct value, the correct type of this search, we're going to alias this as the type that we have here product filters, this comes from our products on API, these filters over here, I want to use those as our state variables to get the same types and the same functionality that we had previously. So what we can do is we can do as product filters and import them from API slash product.
And I believe here, we're going to have to pass search like this. There we go, we have our search done. After that, we're going to want to do exactly the same thing for the category.
So we're going to do const category equals search params dot get category. And we're also going to alias this as product filters. And we're going to pass your category like so the actual type of product filters category is this string literal over here, it can be first, second or third.
And for the search, it is going to be a string. And all of these are optional. So actually, this works because the search params dot get can also return a null value, which fits with the optional type that we have here.
So that's great. And then we just need to do our max price. So we're going to do const max price equals search params dot get max price.
And here, we're going to have to do is because max price is a number, but inside of the URL, everything is a string, we're going to have to actually convert this to an actual number. And to do that, we need to first check if we actually have search parameters max price. So I'm going to do question mark over here.
And then if we have it, we're going to do parse int. And then we're going to do search params dot get and then max price. And then here, otherwise, we're going to return on defined to match with our actual state.
And here, yes, we need to alias this as a string. I know it's not really efficient. But because it doesn't know that we actually have the value over here, I'm just going to alias this as a string, we could also do a little bit more fancy code to actually make this work.
But for the purposes of this throw video, this is perfectly fine. So now we have search, we have category, we have max price. And these are the same values that we had previously in our actual state variables.
But now they're coming directly from the URL. So we're no longer using state from react, we're just using the search parameters over here from your search params from react router, which is coming from the URL. So that's great.
We're one step closer to refactoring this component. Then once we have our values, we actually need to also use set search parameters, which means that we need a function to be able to update these values inside of the URL. And for that, we just need one function that accepts all of these properties.
And based off of those, we'll actually just set them and update them in the state. Let's do it. So I'm going to come here and I'm going to create const set filters.
And that is going to be and here I'm going to do something that I always do. And this is a good pattern to do in your react applications, I'm going to use use callback like this. And I'm going to do this as a function that uses use callback with no dependency array.
The reason I'm doing this is because we're going to be returning this function from our actual hook to be used in any component in the productless filters component in the app component, or anywhere else. And the thing is, whenever you're creating a custom hook, and you have a function here that you're returning, you want to always put use callback because you cannot control where that function is being used in other components. And if you have another component, for example, the productless filters wants to use this set filters function and put it inside of a use effect, which is a valid use case that they might have, they would have to wrap it inside of use callback.
So we don't want to have them do that, right, we want to do it instead ourselves as the owners of the custom hook. Right, and do that extra step for whoever else is going to be using it. This is also what third party libraries do.
So remember to keep it simple. If you're creating a function inside of a custom hook to be reusable, always wrap it in useCallback to prevent anyone else from having to actually do it in other places. And in here, this function is going to take in the filters.
And that is going to be product filters like this. So that's simple enough. And then based on these filters, we're actually going to do some code.
And what I'm going to do here just to save a little bit of time, I'm just going to take some code that I already have to make things a little bit simpler. So I'm going to come here and take this code and just paste it here and walk you through what this actually does. So this uses the setSearchParameters function that we got from useSearchParams.
This one receives the actual parameters that are currently in the URL. And then based off of those, we can actually do things to it. So here we're checking the search if we have filters.
search, if it's equal, if it's not equal to undefined, because we also want to account for an empty string, we're setting params. search to filters. search.
And then we're doing the same thing with the category and the same thing with the max price. And finally, we're returning the final parameters. So that's it, that's all that we need to do for the setFilters function.
And then the last thing for us to do inside of this custom hook is to just return all of these values over here. So we're going to come here and we're going to do return. And we're going to return search, we're going to return category, we're going to return max price, and we're going to return set filters to be able to use these in different components.
So now that we have this custom hook, everything else is pretty simple, because all of the functionality is inside of this custom hook, which we can just use. So we can come here to the app component, we can actually get rid of all of these state variables here, we don't need them. And we can just do const search, category, max price equals use product filters.
And we can just call it directly. And that's it, this component, we have no other functionality, we get the same variables over here, we're passing them to react query in exactly the same way. And we're fine, right.
And now what we just need to do is actually remove this on change here, because we no longer need it, we no longer need an on change inside of this component to pass it to our product filters component. So we can just remove it and save this. And now that's it, we're going to have an error here, because our type still expects an on change, we're going to fix that in just a moment, this actually made our component a little bit simpler, we did a little bit of refactoring, and our code is actually cleaner, which is a good thing, then we can come in here to our product, this filters component, and we can do exactly the same exact thing.
But before we do that, I just want you to keep in mind and remember that we have this debounce search over here, which basically just delays the updating of the search until after the user has finished typing. And it's this debounce search that we're using inside of the use effect over here. So just remember that before we refactor.
So like we did before, we're going to take all of this code, we're going to remove it, and we're just going to do const search, category, and then max price. And we're going to make this equal to use product filters exactly in the same way. And now we actually no longer need this use effect, because we no longer need to call this on change function.
So this is redundant, we can just remove this altogether, great. And now we can actually remove the on change here from the actual props, we even don't even need props in this component anymore, because we have nothing to pass. So let's just remove that.
And now our product list filters component is also simpler. So not the only thing that we have to do is just to fix this part over here, the actual changing of the values. But that's super simple to do, because we also have a set filters function that we can use.
So in here, what we can do instead of set search, we can do set filters. And instead of passing a target that value, we can just pass search, and then a target that value. That's it simple, we're done, let's do the same thing over here, set filters, and then take all of this, and then category, and then paste this, and we're done, we can move on, set filters, take all of this, and this is going to be the max price.
And we're just going to set those like this. And we are done. So already with this code, we are pretty much done.
And we have the same functionality, but now everything is being driven from the URL. So let's see how this works. So here we have our app, it looks exactly the same.
But now if I type something here, like product, we get to see that inside of the search variable over here inside of the URL, actually, we have search equals product, that's great. This works exactly as we expect. If I said a category, for example, second over here, we also get category second, if I set a max price, like 500, we also get 500.
And our products are still being filtered correctly. What's even better is now if I refresh this page, we get our state persisted product persist, the category persist, the max price persist. And if we were to take this URL, send it to someone else, they would see exactly the same screen with the same filters.
And this is efficient. This is why you want to use the URL as state and react for things like this, because it makes sense, you're persistent, you can reload, and you can send this to other people. And the code that we use to actually make this work was super simple.
And it was just one custom hook to actually get this working. This is great. Now there's one more thing that we have to fix in here, because we actually have reduced functionality, as opposed to how it was before.
And that has to do with this use the bounce over here. Remember the debunk search, I told you to remember that we have to deal with this. Because now if you go to our application, you see here, we have product, every time that I change anything on the input, we're instantly showing a search a loading over here, right?
If I just reload, every time I type something, right, as I type every single keystroke, we're actually sending a fetch request to the back end. And that's bad, we actually want to delay this. And that's why we had to use the bounce before.
But now we no longer have it. So we need to reintroduce it. Now to do that, to reintroduce it, I'm going to show you a pattern that I like to use in a lot of my react projects, what we're going to do is we're going to come here, we're going to create a state variable, call it const, local search, and then set local search, that is going to be equal to use state, we're going to reintroduce you state in this actual component, we're also going to give it here product filters, and we're going to give it here the search.
And we're going to do what happened, let's go back, we're going to close this over here. And we're going to default this to undefined like this, this is the first step, this is going to be the local search the search that is only local and will only change within this product filters component, it is never going to make it to the actual URL, then inside of our input over here, instead of setting the filters directly, we actually want to revert to what we had before. And we want to set the local search instead.
So we're going to do set local search instead. And we're going to put it back to exactly how it was before. There's a reason we're doing this.
And it's because we want to have this functionality of listening to the delayed update of our actual search. And then we're going to have to replace the search over here with the local search like this. Now what's going to happen with this component is this is only locally going to update, it's no longer going to trigger the filters themselves.
And it's no longer going to trigger actually refetching the data. So if I go back here, and I actually look at it, now when I change anything to product, nothing is happening, we just have the local search updating, which is what we want, we want this input to update as fast as it can locally. And then after some delay, we want to actually update the URL with the value of the search.
And to do that, the last thing that we need to do is reintroduce our use effect. So we're going to do use effect like this, we're going to pass it here a function like this. Here as the dependency array, we're going to pass the bounced actually, wait, we need to do one more thing, right, we need to use our D bounce, we're going to do const, the bounce search like this use the bounce, and we're going to pass it to your local search.
And actually, we made a mistake here, we need to put search, we need to default and sync the local search with the search. So whenever we're creating this component, we are going to use the value from the URL. That's why we have it there.
That's why we created this custom hook in the first place, we're going to use that value in the URL, we're going to pass it here to our local state to be used as the initial value. And then from that point on, we're going to take over the actual modifying of that value. And we're going to handle it ourselves manually.
And again, the only reason we're doing this is because we want to have two different states, one that is local one that is only for the component to actually see the updates in real time. And the other one for the URL, which we get to control and change at a later point. So then here, we're going to pass the bounce search like this.
And inside of here, all we're going to do is we're going to do set filters. And we're going to do search. And that is going to be debounced search like this.
So what we've done, we created this local search over here, once again, just for this input, this is going to work correctly with the input, the input receives the local search as a value, and then on change only updates that local search. And then we're using the use debounce hook, which is listening to local search, it is then going to delay the update and store it inside of this debounce search. And then we're using it here to actually call it to our set filters, this is going to then set it and commit it to you to the URL, which is going to trigger all of the other functionality.
We're only doing this because we want the local search and the search in the URL to not be in sync and to have different values. So we have to do this, we have to add this complexity to our component. So now I can come here to my input, I can actually change.
And as long as I'm changing, notice that the input itself is actually changing, I'm just doing it super fast. But as soon as I stop changing it, then does the URL actually change, right? Again, I can type anything, the URL is not in itself changing, as you can see here, the moment I stop, then that change gets committed to the actual URL, right.
So we have two different state variables for two different purposes. But they're synced together in a way that we control. That is why we're using this code over here.
I hope that you get it. And that's it. With this, you've actually learned a lot in this video, you've learned how to correctly create a custom hook, put all of the URL related functionality to it, you've learned how to get and derive your state from the actual URL, and then set it in the URL over here.
You've also learned how to use callback whenever you're putting functions that have custom hooks that are meant to be reused, which is a great thing to always do. And then we've simplified this component over here by just pulling the values from the URL through the custom hook, we've added our own custom functionality over here, which again, we had to, and we've made our app component really simple, but with the same functionality, right, that is a sign of a good developer, that is the sign of efficient code. And that is what you should do as a developer, you should always strive to keep things very simple to improve things as much as you can and to write good and clean code, which this is a great example.
And what I would recommend that you do is you take this code, you can find the repository for it in the description, play around with it, see how this works, really try to understand it because there's a ton of lessons to learn here and hopefully that is going to make you a better React developer. If you enjoyed this video and you want to see more videos just like this one, make sure to leave this video a big thumbs up. You can also click here to subscribe or you can click here to watch a different video of mine that YouTube seems to think that you're really going to enjoy.
And with that being said, my name has been Darius Cosden, this is Cosden Solutions. Thank you so much for watching and I will see you in the next one. Ciao, ciao.