TLDR; - I wrote a hook that makes it easy to manage URL query parameters with React. View it on Github or Code Sandbox.
There have been multiple times I've been working on projects and have needed to get and set query parameters in the URL. I tried watching the URL with useEffect
, but that led to way too many bugs and messy code. Eventually, I decided to create a simple hook that would take away all the pain of getting and setting query parameters!
I put all this code in a file in my projects and just import it whenever I need to use it. In fact, you can just copy and paste the following code block to immediately simplify query parameter management in your own project!
// useQueryParam.ts
import { useState } from 'react';
const getQuery = () => {
if (typeof window !== 'undefined') {
return new URLSearchParams(window.location.search);
}
return new URLSearchParams();
};
const getQueryStringVal = (key: string): string | null => {
return getQuery().get(key);
};
const useQueryParam = (
key: string,
defaultVal: string
): [string, (val: string) => void] => {
const [query, setQuery] = useState(getQueryStringVal(key) || defaultVal);
const updateUrl = (newVal: string) => {
setQuery(newVal);
const query = getQuery();
if (newVal.trim() !== '') {
query.set(key, newVal);
} else {
query.delete(key);
}
// This check is necessary if using the hook with Gatsby
if (typeof window !== 'undefined') {
const { protocol, pathname, host } = window.location;
const newUrl = `${protocol}//${host}${pathname}?${query.toString()}`;
window.history.pushState({}, '', newUrl);
}
};
return [query, updateUrl];
};
export default useQueryParam;
Using it in components is easy (Code Sandbox):
import React from 'react';
import useQueryParam from './useQueryParam';
const App = () => {
const [search, setSearch] = useQueryParam('search', '');
return (
<input
value={search}
onChange={({ target: { value } }) => setSearch(value)}
/>
);
};
That's it! useQueryParam
takes two arguments - the first is the name of the parameter, and the second is the default value to be assigned in case the parameter is not present in the URL. If you were just looking for an easy way to manage query parameters in React, you can stop reading. Just copy the code block above and you're good to go. If you'd like to know a little more about how it works, then keep reading.
How it Works
Let's look at the first two functions:
const getQuery = () => {
if (typeof window !== 'undefined') {
return new URLSearchParams(window.location.search);
}
return new URLSearchParams();
};
const getQueryStringVal = (key: string): string | null => {
return getQuery().get(key);
};
getQuery
just returns an instance of URLSearchParams, which just contains a mapping of URL query names to their respective values. Note that for use with SSRs like Gatsby, you must check for the existence of window
.
getQueryStringVal
just gets the value of a specific parameter in the URL. We can use these two functions to craft the actual useQueryParam
hook. It's got two parts, let's examine those. Here's the first part at the beginning of the hook:
const [query, setQuery] = useState(getQueryStringVal(key) || defaultVal);
We use our getQueryStringVal
to get the value of the query parameter, and initialize query
to defaultVal
in case key
doesn't exist in the URL. This will store the value of the parameter, now we just need a function to update it:
const updateUrl = (newVal: string) => {
setQuery(newVal);
const query = getQuery(); // Get the URLSearchParams object
// Update URLSearchParams object
if (newVal.trim() !== '') {
query.set(key, newVal);
} else {
query.delete(key);
}
// This check is necessary if using the hook with Gatsby
if (typeof window !== 'undefined') {
// Update URL
const { protocol, pathname, host } = window.location;
const newUrl = `${protocol}//${host}${pathname}?${query.toString()}`;
window.history.pushState({}, '', newUrl);
}
};
This is the function we return from the hook for updating the URL. We first update the variable we created with useState
. We then use the set
method on URLSearchParams
to update the mapping. Finally, we use the pushState function on window.history
to update the URL without the page refreshing.
That's it! This hook may not necessarily address every possible edge case that could come up when working with query parameters. However, it's worked great for me so far. Feel free to use it in your own projects!
Top comments (5)
Hi Brett, thanks for sharing your code with us.
When I copy and paste your code in my own project though, I get this linting error:
"Parsing error: Cannot read property 'map' of undefined"
I think it has to do with this line of code: "const useQueryParam = ( key: string, defaultVal: string): [string, (val: string) => void] => {"
When I remove the part ": [string, (val: string) => void]" the error message is gone, but things don't work anymore.
I'm new to Typescript, could you explain what is happening in this line of code and perhaps share any ideas on how to fix the linting error?
Thanks in advance.
Remco Hoff
I think you may also need to listen for changes and apply them. What happens if you set a query param and then press back in the browser?
Or you can just use npmjs.com/package/use-query-params
Thanks for sharing this! I just implemented it successfully in a project of mine—took a while to find some advice on how to do this without React Router.
Thank you buddy its amazing. I just slightly changed it by memorizing update url function.