DEV Community

Cover image for The Easiest Way to Use Query Parameters in React
Brett Fisher
Brett Fisher

Posted on

The Easiest Way to Use Query Parameters in React

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!

Discussion (4)

Collapse
remcohoff profile image
Remco Hoff

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

Collapse
cmmartin profile image
Charlie Martin

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?

Collapse
psnehanshu profile image
Snehanshu Phukon
Collapse
tvanantwerp profile image
Tom VanAntwerp

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.