35

I'm using react-router v6. I want to navigate to a URL that has searchParams, but I'm not seeing a way to do this out of the box. useNavigate allows me to navigate to a URL by passing in a string. useSearchParams allows me to set searchParams on the current page.

I could generate the searchParams using createSearchParams and then convert it to a string and append it to the end of the URL with a ? in between, but that seems like a hack.

I'd like to be able to do something like:

const navigate = useNavigate();

// listing?foo=bar
navigate("listing", {
    params: {
        foo: "bar"
    }
});

My hacky workaround:

function useNavigateParams() {
    const navigate = useNavigate();

    return (url: string, params: Record<string, string | string[]>) => {
        const searchParams = createSearchParams(params).toString();
        navigate(url + "?" + searchParams);
    };
}

const navigateParams = useNavigateParams();

navigateParams("listing", {
    foo: "bar"
});

Did I miss something from the documentation?

Drew Reese
  • 165,259
  • 14
  • 153
  • 181
caseyjhol
  • 3,090
  • 2
  • 19
  • 23
  • 1
    to make things less confusing for this conversation, in react-router speak "params" are the variables in the URI aka `/:id` is `params.id`, what you're talking about is commonly refered to as the `location.search` part aka `query-string`. there's a nice helper library that's fairly common for these ops, but I don't think that react-router ships with this stringing / parsing utilites https://www.npmjs.com/package/query-string – azium Jan 19 '21 at 23:03
  • As the question appears to be about `react-router-v6`, you might want to update the tag (v4) – bilo-io Jan 19 '21 at 23:03
  • you can use 'useRouter' from (https://usehooks.com) – Omer Jan 19 '21 at 23:35

6 Answers6

46

Update

It's no longer necessary to prepend ? to search (as of ~September 2021):

import { createSearchParams, useNavigate } from "react-router-dom";

...

const navigate = useNavigate();

navigate({
    pathname: "listing",
    search: createSearchParams({
        foo: "bar"
    }).toString()
});

This isn't quite as simplified as I'd like, but I think it's the closest we can get currently. navigate does support passing in a search query string (not an object).

import { createSearchParams, useNavigate } from "react-router-dom";

...

const navigate = useNavigate();

navigate({
    pathname: "listing",
    search: `?${createSearchParams({
        foo: "bar"
    })}`
});

Source: https://github.com/ReactTraining/react-router/issues/7743#issuecomment-770296462

caseyjhol
  • 3,090
  • 2
  • 19
  • 23
  • The above quoted method works for the first time .i.e, from Parent1 > child1 When I use the same logic, by using Parent2, Child2, its getting navigated to "Child1" instead of "Child2" The route set for parent1 and child 1 is } /> } /> } /> } /> The params for Child 1 and Child2 are set as quoted in github navigate ({pathname: "listing",search: '?' + createSearchParams({ foo: "bar" })}) – rsb Dec 18 '21 at 18:44
  • Hello, I tried using your approach but whenever it redirects, the query parameters are removed. Do you have any idea why? Thank you! – maryyyyyyy Oct 18 '22 at 06:51
12

What you have is looks fine to me. Using the generatePath and createSearchParams utilities it may be a little cleaner, but it is still the same basic idea.

import { generatePath, useNavigate } from "react-router-dom";

...

const useNavigateParams = () => {
  const navigate = useNavigate();

  return (url: string, params: Record<string, string | string[]>) => {
    const path = generatePath(":url?:queryString", {
      url,
      queryString: createSearchParams(params).toString()
    });
    navigate(path);
  };
};

If you think about it this isn't much of a hack, the URL needs to be defined somewhere, whether it's the path params or part of the query string, you still need to provide that detail and build a path string to navigate to.

Demo - POC

Edit react-router-v6-navigate-to-a-url-with-searchparams

RRDv6.4 update

import { createSearchParams, useNavigate } from "react-router-dom";

...

const useNavigateParams = () => {
  const navigate = useNavigate();

  return (pathname, params) => {
    const path = {
      pathname,
      search: createSearchParams(params).toString()
    };
    navigate(path);
  };
};

Edit react-router-v6-navigate-to-a-url-with-searchparams (forked)

Drew Reese
  • 165,259
  • 14
  • 153
  • 181
  • Hello, I tried using your approach but whenever it redirects, the query parameters are removed. Do you have any idea why? Thank you! – maryyyyyyy Oct 18 '22 at 06:56
  • @maryyyyyyy Dunno, we would probably need to see your code to see what it's doing. If you like you can create a new post on stackoverflow with the relevant [mcve] and details regarding the issue and any debugging steps you've taken. Feel free to ping me here in a comment with a link to your post and I can take a look when available. – Drew Reese Oct 18 '22 at 07:21
11

To do this elegantly, you should use the useSearchParams hook instead of useNavigate.

As specified in the doc:

The setSearchParams function works like navigate, but only for the search portion of the URL. Also note that the second arg to setSearchParams is the same type as the second arg to navigate.

import { useSearchParams } from "react-router-dom";

...

const [searchParams, setSearchParams] = useSearchParams()

...

const handleClick = () => {
    searchParams.set('foo', 'bar');
    setSearchParams(searchParams)
}
Fire Druid
  • 246
  • 2
  • 9
  • 3
    Using `setSearchParams()` at the same time as `navigate()` does not work, the route gets messed up. – derpedy-doo Jul 11 '22 at 23:48
  • @derpedy-doo that's the problem I've faced too, one always overwrites the other so you either navigate to the new path *or* you get the search params on the current path – Jordan Mackie Jun 01 '23 at 19:47
1

There is a chance you might want to preserve the search in the URL and only change some values. useSearchParams doesn't seem to work for this purpose, because the first value returned in the hook is an empty object, while it would make more sense for it populated with the search params which are already in the URL. Bad design imho. However, I found a way to achieve the expected behaviour and only change a portion of the parameters using the react router in combination with serialize-query-params

// this goes right below the component declaration,
// where all the hooks are called
const navigate = useNavigate();

....
// this goes inside the function to change the search
// here we merge the new param with the current ones
const newLocation = updateInLocation(
  { foo: 'bar' },
  location
);

navigate(newLocation);
feychu
  • 1,284
  • 1
  • 14
  • 33
0

you can use both of them in an easy way:

import { useNavigate, useSearchParams } from "react-router-dom";

...

const [searchBarParams, setSearchBarParams] = useSearchParams();
const navigate = useNavigate();

...


const handleChange = (event, value) => {
    navigate("/")
    searchBarParams.set("foo",value)
    setSearchBarParams(searchBarParams);
}
abdo afage
  • 26
  • 4
0

Tested with react-router-dom@6.14.0

const navigate = useNavigate();
navigate({ pathname: '/otherpage/', search: "?param1=123" });

Or with createSearchParams from react-router-dom

import { createSearchParams } from "react-router-dom";
const navigate = useNavigate();
navigate({ 
  pathname: '/otherpage/', 
  search: createSearchParams({ params1: "123" }).toString() 
});
Itay B
  • 1
  • 1