'How do you programmatically update query params in react-router?
I can't seem to find how to update query params with react-router without using <Link/>
. hashHistory.push(url)
doesn't seem to register query params, and it doesn't seem like you can pass a query object or anything as a second argument.
How do you change the url from /shop/Clothes/dresses
to /shop/Clothes/dresses?color=blue
in react-router without using <Link>
?
And is an onChange
function really the only way to listen for query changes? Why aren't query changes automatically detected and reacted-to the way that param changes are?
Solution 1:[1]
Within the push
method of hashHistory
, you can specify your query parameters. For instance,
history.push({
pathname: '/dresses',
search: '?color=blue'
})
or
history.push('/dresses?color=blue')
You can check out this repository for additional examples on using history
Solution 2:[2]
Example using react-router v4, redux-thunk and react-router-redux(5.0.0-alpha.6) package.
When user uses search feature, I want him to be able to send url link for same query to a colleague.
import { push } from 'react-router-redux';
import qs from 'query-string';
export const search = () => (dispatch) => {
const query = { firstName: 'John', lastName: 'Doe' };
//API call to retrieve records
//...
const searchString = qs.stringify(query);
dispatch(push({
search: searchString
}))
}
Solution 3:[3]
John's answer is correct. When I'm dealing with params I also need URLSearchParams
interface:
this.props.history.push({
pathname: '/client',
search: "?" + new URLSearchParams({clientId: clientId}).toString()
})
You might also need to wrap your component with a withRouter
HOC eg. export default withRouter(YourComponent);
.
Solution 4:[4]
You can use the replace functionality instead of pushing a new route on every change
import React from 'react';
import { useHistory, useLocation } from 'react-router';
const MyComponent = ()=>{
const history = useHistory();
const location = useLocation();
const onChange=(event)=>{
const {name, value} = event?.target;
const params = new URLSearchParams({[name]: value });
history.replace({ pathname: location.pathname, search: params.toString() });
}
return <input name="search" onChange={onChange} />
}
This preserves the history instead of pushing a new path on every change
Update - February 2022 (V6)
As pointed out by Matrix Spielt useHistory was replaced by useNavigate to make the changes. There is also a convenient method for this called useSearchParams I only got to read the documentation and have not run this but this should work
import React from 'react';
import { useSearchParams } from 'react-router-dom';
// import from react-router should also work but following docs
// import { useSearchParams } from 'react-router';
const MyComponent = ()=>{
const [searchParams, setSearchParams] = useSearchParams();
const onChange=(event)=>{
const {name, value} = event?.target;
setSearchParams({[name]: value})
}
return <input name="search" onChange={onChange} />
}
Solution 5:[5]
for react-router v4.3
const addQuery = (key, value) => {
let pathname = props.location.pathname;
// returns path: '/app/books'
let searchParams = new URLSearchParams(props.location.search);
// returns the existing query string: '?type=fiction&author=fahid'
searchParams.set(key, value);
this.props.history.push({
pathname: pathname,
search: searchParams.toString()
});
};
const removeQuery = (key) => {
let pathname = props.location.pathname;
// returns path: '/app/books'
let searchParams = new URLSearchParams(props.location.search);
// returns the existing query string: '?type=fiction&author=fahid'
searchParams.delete(key);
this.props.history.push({
pathname: pathname,
search: searchParams.toString()
});
};
function SomeComponent({ location }) {
return <div>
<button onClick={ () => addQuery('book', 'react')}>search react books</button>
<button onClick={ () => removeQuery('book')}>remove search</button>
</div>;
}
To know more on URLSearchParams from Mozilla:
var paramsString = "q=URLUtils.searchParams&topic=api";
var searchParams = new URLSearchParams(paramsString);
//Iterate the search parameters.
for (let p of searchParams) {
console.log(p);
}
searchParams.has("topic") === true; // true
searchParams.get("topic") === "api"; // true
searchParams.getAll("topic"); // ["api"]
searchParams.get("foo") === null; // true
searchParams.append("topic", "webdev");
searchParams.toString(); // "q=URLUtils.searchParams&topic=api&topic=webdev"
searchParams.set("topic", "More webdev");
searchParams.toString(); // "q=URLUtils.searchParams&topic=More+webdev"
searchParams.delete("topic");
searchParams.toString(); // "q=URLUtils.searchParams"
Solution 6:[6]
You can use hook useHistory
Make sure that you're using function
based component
Import this at the top
import {useHistory} from "react-router-dom"
In your component,
const history = useHistory()
history.push({
pathname: window.location.pathname,
search: '?color=blue'
})
Solution 7:[7]
From DimitriDushkin on GitHub:
import { browserHistory } from 'react-router';
/**
* @param {Object} query
*/
export const addQuery = (query) => {
const location = Object.assign({}, browserHistory.getCurrentLocation());
Object.assign(location.query, query);
// or simple replace location.query if you want to completely change params
browserHistory.push(location);
};
/**
* @param {...String} queryNames
*/
export const removeQuery = (...queryNames) => {
const location = Object.assign({}, browserHistory.getCurrentLocation());
queryNames.forEach(q => delete location.query[q]);
browserHistory.push(location);
};
or
import { withRouter } from 'react-router';
import { addQuery, removeQuery } from '../../utils/utils-router';
function SomeComponent({ location }) {
return <div style={{ backgroundColor: location.query.paintRed ? '#f00' : '#fff' }}>
<button onClick={ () => addQuery({ paintRed: 1 })}>Paint red</button>
<button onClick={ () => removeQuery('paintRed')}>Paint white</button>
</div>;
}
export default withRouter(SomeComponent);
Solution 8:[8]
Using query-string module is the recommended one when you need a module to parse your query string in ease.
componentWillMount() {
var query = queryString.parse(this.props.location.search);
if (query.token) {
window.localStorage.setItem("jwt", query.token);
store.dispatch(push("/"));
}
}
Here, I am redirecting back to my client from Node.js server after successful Google-Passport authentication, which is redirecting back with token as query param.
I am parsing it with query-string module, saving it and updating the query params in the url with push from react-router-redux.
Solution 9:[9]
It can also be written this way
this.props.history.push(`${window.location.pathname}&page=${pageNumber}`)
Solution 10:[10]
I prefer you to use below function that is ES6
style:
getQueryStringParams = query => {
return query
? (/^[?#]/.test(query) ? query.slice(1) : query)
.split('&')
.reduce((params, param) => {
let [key, value] = param.split('=');
params[key] = value ? decodeURIComponent(value.replace(/\+/g, ' ')) : '';
return params;
}, {}
)
: {}
};
Solution 11:[11]
In my case typing into input field outputs it into browser's url as a query string, using React JS functional component as shown below
import React, { useEffect, useState } from 'react'
import { useHistory } from 'react-router-dom'
const Search = () => {
const [query, setQuery] = useState('')
const history = useHistory()
const onChange = (e) => {
setQuery(e.target.value)
}
useEffect(() => {
const params = new URLSearchParams()
if (query) {
params.append('name', query)
} else {
params.delete('name')
}
history.push({ search: params.toString() })
}, [query, history])
return <input type="text" value={query} onChange={onChange} />
}
export default Search
browser's URL Query
/search?name=query_here
Solution 12:[12]
Like @Craques explained we can use the replace functionality instead of pushing a new route on every change. However, in version 6 of react-router, useHistory() was by replaced useNavigate(), which returns only a function. You can pass options to the function, to achieve the same effect as the old location.replace():
import { useLocation, useNavigate } from 'react-router-dom';
const to = { pathname: location.pathname, search: newParams.toString() };
navigate(to, { replace: true });
Solution 13:[13]
I've made a simple hook to ease up the job.
Lets imagine your url is something like this:
/search?origin=home&page=1
function useUrl(param: string) {
const history = useHistory()
const { search, pathname } = useLocation()
const url = new URLSearchParams(search)
const urlParam = url.get(param)
const [value, setValue] = useState(urlParam !== null ? urlParam : '')
function _setValue(val: string){
url.set(param, val)
history.replace({ pathname, search: url.toString() });
setValue(val)
}
return [value, _setValue]
}
Then the actual usage:
function SearchPage() {
const [origin] = useUrl("origin")
const [page, setPage] = useUrl("page")
return (
<div>
<p>Return to: {origin}</p>
<p>Current Page: {page}</p>
</div>
)
}
Solution 14:[14]
I'm currently on react-router
v5 in a running project and cannot easily migrate to v6.
I wrote a hook that allows to read and modify a single URL param while leaving the other URL params untouched.
Arrays are treated as lists of comma separated values:
?products=pipe,deerstalker,magnifying_glass
import { useCallback } from 'react';
import { useHistory } from 'react-router';
const getDecodedUrlParam = (name: string, locationSearch: string, _default?: any) => {
const params = deserialize(locationSearch);
const param = params[name];
if (_default && Array.isArray(_default)) {
return param
? param.split(',').map((v: string) => decodeURIComponent(v))
: _default;
}
return param ? decodeURIComponent(param) : _default;
};
const deserialize = (locationSearch: string): any => {
if (locationSearch.startsWith('?')) {
locationSearch = locationSearch.substring(1);
}
const parts = locationSearch.split('&');
return Object.fromEntries(parts.map((part) => part.split('=')));
};
const serialize = (params: any) =>
Object.entries(params)
.map(([key, value]) => `${key}=${value}`)
.join('&');
export const useURLSearchParam = (name: string, _default?: any) => {
const history = useHistory();
const value: any = getDecodedUrlParam(name, location.search, _default);
const _update = useCallback(
(value: any) => {
const params = deserialize(location.search);
if (Array.isArray(value)) {
params[name] = value.map((v) => encodeURIComponent(v)).join(',');
} else {
params[name] = encodeURIComponent(value);
}
history.replace({ pathname: location.pathname, search: serialize(params) });
},
[history, name]
);
const _delete = useCallback(() => {
const params = deserialize(location.search);
delete params[name];
history.replace({ pathname: location.pathname, search: serialize(params) });
}, [history, name]);
return [value, _update, _delete];
};
Solution 15:[15]
react-router-dom v5 solution
import { useHistory } from 'react-router-dom';
const history = useHistory(); // useHistory hook inside functional component
history.replace({search: (new URLSearchParams({activetab : 1})).toString()});
it is recommended to use URLSearchParams as it can take care of spaces and special chars in query params while encoding and decoding the query params
new URLSearchParams({'active tab':1 }).toString() // 'active+tab=1'
new URLSearchParams('active+tab=1').get('active tab') // 1
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow