Ever wonder how apps seem to remember filters, searches, or specific pages even after refreshing or sharing the link?
Well, that's the magic of search parameters, and with Remix’s useSearchParams
, you can add that same kind of smooth, user-friendly experience to your own apps.
If you're not familiar with search parameters, don't worry - they're just a way to store and share information right in the URL. And once you get the hang of useSearchParams
, you'll see how it makes things like filtering, searching, and even pagination a breeze.
In this guide, I'm going to break down what search parameters are, why they matter, and how to use useSearchParams
in your Remix projects. We'll also dive into some easy-to-follow examples to help you see it in action.
Ready to make your Remix apps even more powerful? Let’s get into it!
Sidenote: If you enjoy this guide and want to take an even deeper dive in Remix, then check out my complete Remix course:
If you're a Developer who is serious about taking your coding skills and career to the next level by building better websites, then this is the course for you.
This course covers everything from beginner to advanced topics and building your own full-stack project!
With that out of the way, let’s get into the guide…
useSearchParams
is a hook provided by Remix that lets you work with search (or query) parameters in a URL. If you've used React Router before, this will feel familiar because it's built on top of that package.
If you’ve never used that then let’s break it down.
In simple terms, search parameters are the little bits of info in a URL that appear after the question mark (?
).
They store and share things like filters or search terms right in the URL, making your app more user-friendly, as they allow the app to remember a user's choices, even if they refresh or share the link.
For example
Imagine a user is shopping for pillows on a website, and they want to filter the results to only see large, blue pillows. After applying the filters, the URL might look like this:
https://example.com/pillows?color=blue&size=large
In this case, color=blue
and size=large
are the search parameters. These parameters work as key-value pairs—color
is the key and blue
is the value, while size
is another key with large
as its value. Multiple parameters are separated by an ampersand (&
), as seen here.
Now, here’s where useSearchParams
comes into play.
Remix lets you access and manipulate these parameters directly within your components using useSearchParams
. This hook gives you an array with two items:
URLSearchParams
, which allows you to read the current search parametersNow that we understand the theory, let’s see how it’s done in practice.
Here’s how this works in a practical example:
import { useSearchParams } from '@remix-run/react';
function PillowFilter() {
let [searchParams, setSearchParams] = useSearchParams();
let color = searchParams.get('color') || 'none'; // Default to 'none' if not set
const handleFilterChange = () => {
setSearchParams((prev) => {
prev.set('color', 'green');
return prev;
});
};
return (
<div>
<p>Current Color Filter: {color}</p>
<button onClick={handleFilterChange}>Filter to Green</button>
</div>
);
}
In this example:
searchParams.get('color')
to read the current color
parameter from the URL. This allows the app to dynamically update and sync the user’s filters directly in the URL, ensuring that any user-selected settings—like filters—remain intact, even if the page is refreshed or sharedcolor
parameter is found, it defaults to 'none'
setSearchParams
is used to update the color
parameter in the URL to 'green'
. This ties the app's state (the color filter) directly to the URL, making it easy to maintain and shareBut why is this feature so valuable in a Remix app? That’s where the real benefits of using useSearchParams
come into play…
Let’s break down some of the main advantages that can significantly enhance your app’s user experience and performance.
In React, we often use hooks like useState
or useReducer
to manage state, but these are limited to the client side. This means that the server doesn't know anything about the state being held, which can cause issues when trying to sync between the two.
For example
If you're building a React app that uses useState
to manage filters, and the user refreshes the page, they'll lose their filter settings because the state isn't preserved. Similarly, if they share the URL with someone else, the other person won't see the same filters applied.
Search parameters solve this problem because they live right in the URL. This means that both the server and the client can see them. So when Remix server-renders your application, it knows the exact state of the page because it can read the search parameters from the URL.
This has several benefits:
One of the best things about using search parameters is that they take advantage of built-in web behavior.
For example
When you submit a form with the GET
method, the default behavior is to send a request and append the form data as search parameters in the URL. This allows you to implement features like searching, filtering, or pagination with simple web forms, aligning your app with web standards.
Remix embraces this approach, allowing you to build apps that are both powerful and easy to maintain—no need to reinvent the wheel!
useSearchParams
fits perfectly with the declarative style of React. Instead of writing imperative code that directly manipulates the URL, you describe what the state of your parameters should be and let useSearchParams
handle it.
For example
Let's say you're building a product filter for an e-commerce site.
With useSearchParams
, you don't have to write complex code to manage the URL changes yourself. Instead, you define the filters and simply update the search parameters, and Remix will update the URL for you.
Why is this beneficial?
When you update the search parameters using setSearchParams
, Remix triggers a navigation that re-fetches data from your loaders and re-renders the UI. This means you don't need to manually handle data fetching or worry about syncing the URL with the app state.
For example
Imagine you're working with paginated data, such as browsing through pages of products.
When the user changes the page (e.g., by clicking "Next"), you update the page
search parameter using setSearchParams
. Remix will then automatically trigger a fetch to get the next page of data and render it for the user.
const handleNextPage = () => {
setSearchParams((prev) => {
prev.set('page', nextPage);
return prev;
});
};
Why is this beneficial?
Now that we understand what useSearchParams
is and why it matters, let's dive into how to use it in your Remix application.
Firstly, you need to import useSearchParams
from Remix:
import { useSearchParams } from '@remix-run/react';
Then inside your component, you can use the useSearchParams
hook. It returns an array where the first element is searchParams
- which is an instance of URLSearchParams
.
With searchParams
, you can use its methods, like .get()
, to read the value of a particular search parameter:
function SearchComponent() {
let [searchParams] = useSearchParams();
let query = searchParams.get('q') || '';
return (
<div>
<p>Search Query: {query}</p>
</div>
);
}
In this example, we're getting the value of the q
parameter from the URL.
To update search parameters, you can then use the second element that useSearchParams
returns - the setter function (setSearchParams
):
function SearchComponent() {
let [searchParams, setSearchParams] = useSearchParams();
let query = searchParams.get('q') || '';
function updateQuery(newQuery) {
setSearchParams((prev) => {
prev.set('q', newQuery);
return prev;
});
}
return (
<div>
<input
type="text"
value={query}
onChange={(e) => updateQuery(e.target.value)}
/>
</div>
);
}
In this example, we're updating the q
parameter in the URL whenever the input value changes.
Let's now take a look at some practical examples and use-cases of useSearchParams
.
One of the most common use-cases for query parameters is implementing search functionality in your web application.
Users can type a search term, which gets added to the URL as a query parameter. Then, your app reads this search parameter and fetches the relevant data.
Here's a simple example of a search component using useSearchParams
:
import { useSearchParams } from '@remix-run/react';
function Search() {
let [searchParams] = useSearchParams();
let query = searchParams.get('q') || '';
return (
<form method="GET">
<input name="q" defaultValue={query} />
<button type="submit">Search</button>
</form>
);
}
This component consists of a search box where a user can type their search term.
Notice that we’re not using the setSearchParams
function in this example, because you don’t need it!
Instead, when the user submits the form, the browser updates the query parameter q
in the URL with the entered search term because our input is named “q”. This is the default behavior of forms on the web!
Sidenote: I would avoid using
setSearchParams
whenever possible, since it requires you to write and maintain less code. Why write code to do something that forms already know how to do? Let the form take care of updating the search params in this case.
Let’s do an example where we use setSearchParams
instead of using a form.
Query parameters are also used frequently for navigating through paginated resources. You can represent the current page as a query parameter, and useSearchParams
can help set or update this parameter as the user navigates through the pages.
Here's a pagination component example:
import { useSearchParams } from '@remix-run/react';
function Pagination({ total }) {
let [searchParams, setSearchParams] = useSearchParams();
let page = parseInt(searchParams.get('page')) || 1;
function onPageChange(newPage) {
setSearchParams((prev) => {
prev.set('page', newPage);
return prev;
});
}
return (
<div>
{page > 1 && (
<button onClick={() => onPageChange(page - 1)}>Previous</button>
)}
{page < total && (
<button onClick={() => onPageChange(page + 1)}>Next</button>
)}
</div>
);
}
In this component, we're reading the page
parameter from the URL and providing buttons to navigate to the previous or next page. When a button is clicked, it updates the page
parameter in the URL.
Now, we could write this example without setSearchParams
if we wanted to (like I advised in the previous example). To do that, we’d have to wrap each button in a form and give both buttons the name “page” and set their values to “page - 1” and “page + 1”, like this:
import { useSearchParams } from "@remix-run/react";
function Pagination({ total }) {
let [searchParams] = useSearchParams();
let page = parseInt(searchParams.get("page")) || 1;
return (
<div>
{/* ...render items on current page */}
{page > 1 && <Form><button name=”page” value={page - 1}>Previous</button></Form>}
{page < total && <Form><button name=”page” value={page + 1}>Next</button></Form>}
</div>
);
}
This code is actually a bit simpler than the setSearchParams
version, which is why I recommend using forms to update the search parameters whenever possible.
Personally, I think it more clearly communicates the intention of the code, which is always my first priority, as we want it to be as clear as possible to our future selves and other developers what the code is doing.
So as you can see, useSearchParams
makes managing search parameters in Remix effortless, letting you sync state between the client and server, maintain shareable URLs, and embrace web standards.
With its declarative style, you’ll write cleaner, more maintainable code while ensuring a smooth user experience.
Ready to take your Remix projects to the next level? Start using useSearchParams
to manage your app’s state directly in the URL. Try it out in your own project today and see how much easier and more powerful your development process becomes!
Remember, if you want to take an even deeper dive in Remix, then check out my complete Remix course:
This course covers everything from beginner to advanced topics and building your own full-stack project!
Plus, once you join, you'll have the opportunity to ask questions in our private Discord community from me, other students, and working Web Developers.