250

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?

Patrick Mevzek
  • 10,995
  • 16
  • 38
  • 54
claireablani
  • 7,804
  • 5
  • 16
  • 19
  • 1
    you should be using the history singleton as referred to in [this question](http://stackoverflow.com/questions/31079081/programmatically-navigate-using-react-router) – Joseph Combs May 13 '17 at 08:24

19 Answers19

279

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

John F.
  • 4,780
  • 5
  • 28
  • 40
55

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} />
}
Craques
  • 953
  • 1
  • 8
  • 16
54

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

spedy
  • 2,200
  • 25
  • 26
51

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
    }))
}
  • 20
    `react-router-redux` is deprecated – vsync Oct 15 '18 at 09:46
  • I think this must now be done by rendering the `` tag, link to [docs page](https://reacttraining.com/react-router/web/api/Redirect) – TKAB Oct 25 '18 at 19:25
  • 2
    You can just wrap your component in `withReducer` HOC which will give you the `history` prop. And then you can run `history.push({ search: querystring }`. – sasklacz Apr 23 '19 at 18:22
  • 1
    Instead of `react-router-redux`, you can use `connected-react-router` which is not deprecated. – Søren Boisen Aug 05 '20 at 08:28
14

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"
Martin
  • 5,714
  • 2
  • 21
  • 41
Fahid M
  • 184
  • 1
  • 4
11

For React Router v6+ just use the new useSearchParams hook (specifically setSearchParams):

const [searchParams, setSearchParams] = useSearchParams()

setSearchParams(`?${new URLSearchParams({ paramName: 'whatever' })}`)

For React Router v5 use useHistory:

const history = useHistory()

history.push({
    pathname: '/the-path',
    search: `?${new URLSearchParams({ paramName: 'whatever' })}`
})

Note: URLSearchParams is just an optional (recommended!) convenience helper here, but you can also use just a raw string with url parameters.

Neurotransmitter
  • 6,289
  • 2
  • 51
  • 38
  • So about your *note* and the V6 way: we do can use `setSearchParams({ paramName: 'whatever' })` -> that will be just as fine? – Aleksandar May 27 '23 at 01:51
10

With React Router V6 we can achieve it like this

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

/* In React Component */
const navigate = useNavigate();
const params = {
  color: 'blue',
};
const options = {
  pathname: '/shop/Clothes/dresses',
  search: `?${createSearchParams(params)}`,
};
navigate(options, { replace: true });
Rakesh Sharma
  • 608
  • 6
  • 9
  • 1
    Thanks for the thorough documentation! Knowing that options contains both `pathname` and `search` helped me figure out how I was going to translate the following `history.push({ search: urlSearchParams.toString(), state })` into navigate. I ended up doing `naviate({ search: urlSearchParams.toString()}, { state })`. What I learned is that while previously the `state` property lived in the first parameter of `history.push`, it is part of the second parameter in `navigate`. – forrestDinos Nov 27 '22 at 01:53
  • Included here is the type definition of the navigate function. https://reactrouter.com/en/main/hooks/use-navigate – forrestDinos Nov 27 '22 at 01:55
  • 1
    Also if you only want to update the (query params) search portion of url, then try https://reactrouter.com/en/main/hooks/use-search-params – Rakesh Sharma Dec 07 '22 at 12:38
9

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'
})
Koushik Das
  • 9,678
  • 3
  • 51
  • 50
7

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
Viraj Singh
  • 1,951
  • 1
  • 17
  • 27
4

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);
Neil
  • 24,551
  • 15
  • 60
  • 81
Design by Adrian
  • 2,185
  • 1
  • 20
  • 22
3

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.

Balasubramani M
  • 7,742
  • 2
  • 45
  • 47
2

It can also be written this way

this.props.history.push(`${window.location.pathname}&page=${pageNumber}`)
madhu131313
  • 7,003
  • 7
  • 40
  • 53
2

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 });
Marten
  • 645
  • 7
  • 21
1

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;
                }, {}
            )
        : {}
};
AmerllicA
  • 29,059
  • 15
  • 130
  • 154
1

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

PHANTOM-X
  • 502
  • 6
  • 16
0

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

Martin
  • 5,714
  • 2
  • 21
  • 41
0

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>
    )
}
Kuza Grave
  • 1,256
  • 14
  • 15
0
// react-router-dom v6

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

// useSearchParams hook
const [searchParams, setSearchParams] = useSearchParams();

// usage
const params: URLSearchParams = new URLSearchParams();
params.id = '123';
params.color = 'white';

// set new parameters
setSearchParams(params);

!!! BE AWARE !!! This will only update the query parameters on the current page, but you will not be able to navigate back (browser back btn) to a previous route because this option does not change your history. To have in place this behavior check a previous answer: https://stackoverflow.com/users/6160270/rakesh-sharma 's answer

C. Draghici
  • 157
  • 5
0

like @kuza grave did

to catch the params in version 5

eg lets say your url looks like Mylink?name=johndoe&tenant=code

import { useLocation} from "react-router-dom";

const { search } = useLocation()
const url = new URLSearchParams(search);


let [name,setName] = useState(null);
let [tenant,setTenant] = useState(null);


useEffect(()=>{
  
 setName(url.get('name'));
 setTenant(url.get('tenant'));

 console.log(name,tenant);
},[url,name,tenant]);



// you can use it in your page

Return (
<div>{name}</div>
<div>{tenant}</div>
)
Plumptre Ademola
  • 103
  • 1
  • 2
  • 6
  • As it’s currently written, your answer is unclear. Please [edit] to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Mar 29 '23 at 00:16