caseyjhol
caseyjhol

Reputation: 3898

react-router v6: Navigate to a URL with searchParams

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?

Upvotes: 53

Views: 139484

Answers (8)

L&#225;szl&#243;
L&#225;szl&#243;

Reputation: 872

I wanted to have path interpolation with this, which none of the answers accounted for, so here is a solution that can do it:

import { createSearchParams, generatePath, useNavigate } from 'react-router';

export const useMyNavigate = () => {
    const navigate = useNavigate();
    return (url: string, params: Record<string, string | string[]>) => {
        const restParams = Object.fromEntries(
            Object.keys(params)
                .filter(x => !x.match(/^\w+$/) || !url.match(new RegExp(`:${x}\\b`)))
                .map(x => [x, params[x]])
        );
        navigate({
            pathname: generatePath(url, params),
            search: createSearchParams(restParams).toString(),
        });
    };
};

This allows the following call:

const myNavigate = useMyNavigate();

myNavigate('/news/:newsId', { newsId: 123, highlight: 'some word' });
// navigates to: /news/123?highlight=some+word

As an added bonus, when you define your routes, you can simply export your paths from there and import it where you need it. This way you can change url patterns more easily.

export const newsArticlePath = '/news/:newsId';
<Route path={newsArticlePath} Component={NewsArticlePage} />

...

import { newsArticlePath } from './Routing.js';
myNavigate(newsArticlePath, { newsId: 123, highlight: 'some word' });

Upvotes: 0

Itay
Itay

Reputation: 429

That's how I solved it:

I used the native URLSearchParams because I needed the same key to have more than one value.

const navigate = useNavigate();
const navigateToMyNextPage = () => {
  const searchParams = new URLSearchParams();
  searchParams.append("course", '9877');
  searchParams.append("course", '111'); // same-key again
  searchParams.append("time-of-day", 'morning');

  navigate({
    pathname: "../students",
    search: searchParams.toString(),
  });
}

This will navigate to the students page in my app like this -->>

something/students?course=9877&course=111&time-of-day=morning

Upvotes: 0

Itay B
Itay B

Reputation: 61

Tested with [email protected]

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() 
});

Upvotes: 6

feychu
feychu

Reputation: 1364

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);

Upvotes: 1

abdo afage
abdo afage

Reputation: 24

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);
}

Upvotes: -1

Drew Reese
Drew Reese

Reputation: 202638

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)

Upvotes: 15

caseyjhol
caseyjhol

Reputation: 3898

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

Upvotes: 74

Fire Druid
Fire Druid

Reputation: 298

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)
}

Upvotes: 12

Related Questions