Reputation: 3898
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
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
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
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
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
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
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.
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);
};
};
Upvotes: 15
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
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