In order to retrieve the current query params, I'm using this:
import { useLocation } from 'react-router-dom';
function useQuery() {
return new URLSearchParams(useLocation().search);
}
Then in the functional ponent:
const query = useQuery();
However, I didn't find any integrated solution to easily set Link
to the same query params except one with a new value.
Here is my solution so far:
const filterLink = query => param => value => {
const clone = new URLSearchParams(query.toString());
clone.set(param, value);
return `?${clone.toString()}`;
}
return (
<>
<Link to={filterLink(query)('some-filter')('false')}>False</Link>
<Link to={filterLink(query)('some-filter')('true')}>True</Link>
</>
)
I have to clone the query
object in order not to mutate the original one and have unwanted side effects when calling multiple times filterLink
in the JSX. I also have to add the question mark myself because URLSearchParams.prototype.toString()
does not add it.
I am wondering why I have to do that myself? I don't really like to do so low-level things when using a framework. Did I miss something in react-router? Is there a more mon practice to do what I need?
In order to retrieve the current query params, I'm using this:
import { useLocation } from 'react-router-dom';
function useQuery() {
return new URLSearchParams(useLocation().search);
}
Then in the functional ponent:
const query = useQuery();
However, I didn't find any integrated solution to easily set Link
to the same query params except one with a new value.
Here is my solution so far:
const filterLink = query => param => value => {
const clone = new URLSearchParams(query.toString());
clone.set(param, value);
return `?${clone.toString()}`;
}
return (
<>
<Link to={filterLink(query)('some-filter')('false')}>False</Link>
<Link to={filterLink(query)('some-filter')('true')}>True</Link>
</>
)
I have to clone the query
object in order not to mutate the original one and have unwanted side effects when calling multiple times filterLink
in the JSX. I also have to add the question mark myself because URLSearchParams.prototype.toString()
does not add it.
I am wondering why I have to do that myself? I don't really like to do so low-level things when using a framework. Did I miss something in react-router? Is there a more mon practice to do what I need?
Share Improve this question edited Jan 25, 2023 at 0:41 Drew Reese 204k18 gold badges244 silver badges273 bronze badges asked May 16, 2021 at 21:23 Guerric PGuerric P 31.9k6 gold badges58 silver badges106 bronze badges 3-
What sort of "up-to-date answer" are you looking for? Are you still using
react-router@5
or are you looking for something that now usesreact-router@6
? Is the goal here to still use aLink
ponent that "injects" specific queryString params into the link target path? Do you need to preserve existing query params? Do you want/need the queryString visible as part of the link's accessibility text? – Drew Reese Commented Jan 19, 2023 at 17:20 - I'd like a general answer about the current practices in React. As an Angular developer, I'm used to the Angular HTTP client library that provides a convenient way to handle query params while being integrated to the framework, so I'm wondering what's the equivalent in React – Guerric P Commented Jan 19, 2023 at 19:04
-
I guess my question is directed more to understanding the exact behavior you are wanting/expecting. Angular is a framework while React is considered more a library. As such, React is a little more DYI. React doesn't care about the URL queryString, and
react-router
is mainly interested in the URL path for route matching and rendering. – Drew Reese Commented Jan 20, 2023 at 6:24
3 Answers
Reset to default 6 +100Angular is a framework while React is generally considered to still be a library. As such, React is a little more Do It Yourself (DIY). React doesn't care about the URL queryString, and react-router
is mainly interested in the URL path for route matching and rendering.
However, react-router-dom@6
introduced a few new hooks and utility functions to help work with the queryString parameters. One utility that you may find helpful in this endeavor is the createSearchParams
function.
declare function createSearchParams( init?: URLSearchParamsInit ): URLSearchParams;
createSearchParams
is a thin wrapper aroundnew URLSearchParams(init)
that adds support for using objects with array values. This is the same function thatuseSearchParams
uses internally for creatingURLSearchParams
objects fromURLSearchParamsInit
values.
Based on your question and the other answers here I'm assuming you don't simply need to blow away previously existing search params and replace them with the current link, but rather that you want to possibly conditionally merge new parameters with any existing params.
Create a utility function that takes the same props as the Link
ponent's to
prop (string | Partial<Path>
). It's the partial Path
types we care about and want to override.
import { createSearchParams, To } from "react-router-dom";
interface CreatePath {
pathname: string;
search?: {
[key: string]: string | number;
};
hash?: string;
merge?: boolean;
}
const createPath = ({
hash,
merge = true,
pathname,
search = {}
}: CreatePath): To => {
const searchParams = createSearchParams({
...(merge
? (Object.fromEntries(createSearchParams(window.location.search)) as {})
: {}),
...search
});
return { pathname, search: searchParams.toString(), hash };
};
Usage:
Link that merges params with existing queryString params:
<Link to={createPath({ pathname: "/somePage", search: { a: 1 } })}> Some Page </Link>
Link that replaces existing params:
<Link to={createPath({ pathname: "/somePage", search: { b: 2 }, merge: false })} > Some Page </Link>
I'd advise to take this a step further and create a custom Link
ponent that does path creation step for you.
Example building on the above utility function:
import { Link as LinkBase, LinkProps as BaseLinkProps } from "react-router-dom";
// Override the to prop
interface LinkProps extends Omit<BaseLinkProps, "to"> {
to: CreatePath;
}
// Use our new createPath utility
const Link = ({ to, ...props }: LinkProps) => (
<LinkBase to={createPath(to)} {...props} />
);
Usage is same as above but now the to
prop is directly passed:
Link that merges params with existing queryString params:
<Link to={{ pathname: "/somePage", search: { a: 1 } }}> Some Page </Link>
Link that replaces existing params:
<Link to={{ pathname: "/somePage", search: { b: 2 }, merge: false }}> Some Page </Link>
Demo:
I didn't know react-router
has anything to do with query either. This is the code that i used before to set it.
const _encode = (v) => v.isWellFormed()
? encodeURIComponent(v)
: '';
const queryString = (params) => '?' + Object
.keys(params)
.map((key) => (_encode(key) + '=' + _encode(params[key])))
.join('&')
my two cents, router doesn't actually support these parameters. Maybe useQuery
is just a bit handy, but other than that they don't use any of that when setting up the routes.
This is my oneliner solution
import encodeurl from "encodeurl";
Object.entries(inputObject)
.map(([key, value]) => `${key}=${encodeurl(value)}`)
.join("&");