178
votes

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?

11
you should be using the history singleton as referred to in this question - Joseph Combs

11 Answers

223
votes

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

47
votes

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

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

8
votes
    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:][1]

 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"


[1]: https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams
7
votes

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

3
votes

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

Using query-string module is the recommended one when you need a module to parse your query string in ease.

http://localhost:3000?token=xxx-xxx-xxx

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.

2
votes

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;
                }, {}
            )
        : {}
};
2
votes

It can also be written this way

this.props.history.push(`${window.location.pathname}&page=${pageNumber}`)
1
votes

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

1
votes

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