React-router: Make it easier to add query parameters

Created on 19 Apr 2015  路  26Comments  路  Source: ReactTraining/react-router

In my application I change a query parameter often. The code to do this requires some boilerplate. The code I have currently looks like this:

const router = this.props.router;
const path = router.getCurrentPathname();
const params = router.getCurrentParams();
const query = router.getCurrentQuery();
query.q = this.props.query;
router.transitionTo(path, params, query);

It could work like angular's $location.search

Then the code above could look like:

router.query('q', this.props.query);

I think this could be a useful enhancement.

Most helpful comment

I've end up with these:

import { browserHistory } from 'react-router';

/**
 * @param {Object} query
 */
export const addQuery = (query) => {
    const location = Object.assign({}, browserHistory.getCurrentLocation());
    Object.assign(location.query, query);
    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);
};

And example usage:

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

All 26 comments

I find myself transitioning between a route with slightly different query params a lot as well (like filtering a list). Is this the idiomatic react router way? I would like it to have a little less boilerplate if possible.

0.13.x

<Link to="/somewhere" query={Object.assign({}, this.props.query, newStuff)}/>

1.0

<Link to="/somewhere" query={Object.assign({}, this.props.location.query, newStuff)}/>

Seems pretty good to me, what do you think? If its no good, we can reopen, we get this question quite a bit.

My query parameter changes based on a submission of a search box. This means that I am transitioning programmatically in a onSubmit handler. I don't see how I can use the link component there.

It would make sense if the transitionTo methods would work the same like the link component. Maybe something like:

transitionTo({q: this.props.query}) // only sets query param q

or

transitionTo(null, null, {q: this.props.query}) // sets all query params

A null value would default to the current value. To reset the value you would do something like this:

transitionTo('',{},{});

But honestly I like the solution based on $location.search I suggested in the OP best:

router.query('q', this.props.query); // sets only the query param q
router.query({q: this.props.query}); // overwrites all query params
router.query('q'); // would return the current value of query param 'q'

Therefore I suggest reopening this issue.

My problem with the current Object Assign solution is that if my query parameters is allowed to be a list like "?year=2015&year=2016", the current solution will only take it as and update from "?year=2015" to "?year=2016".
Then I try to do some logic in the render() to calculate the new queryObject as I need to change a string value to an array, calculated obj is wrong as the render() could be trigger other than a click action.
To allow adding logic to change query, I am not aware of any method for now to update it correctly.
I would think a append option for query is needed and the href could be all "?year=2016", the current method will update all the link related to query to be "?xxxxxxxxxx&newquery" which its not what is should be.

Is there any good method for this?

Like @0xR I want to add query params but not have to pass in the current pathname- I have a series of filters and I want to add a query param for every filter selected to enable bookmarking.

To enable something like...

onFilterChange(filterName, val) {
  addQuery({ filterName: val });
}

I'd like to be able to do this without passing in the current pathname every time. Any help appreciated :)

Currently I am totally lost, I would like to change the query params somehow, from a handler, like @robhadfield and I am even not able to use the methods in OP above..

Google searches brings me to context based solutions, but it seems to be deprecated.. I guess there must be a simple way, but I DONT know it, and most of the posts are just and obsolete..

Any update on this?

What I found was this:

      this.context.history.push({
        ...this.props.location,
        query: {someParam: "value"}
      });

So you can create programmatically your query with the old params.. and then assign it..

var query = { ...this.props.location.query, newParam: "blub" }

      this.context.history.push({
        ...this.props.location,
        query
      });

dont forget to add history to the contextTypes ;)

@dropfen Where in your code did you use this? The place where you trigger the route transition I guess?

I am using it in the components methods, which get called on events like changing a dropdown filter..

Ok, thank you! :)

you are welcome :)

Yes, and see the v3 alpha changelog, which makes <Link to> also take a function, so you can do to={location => ({ ...location, query: nextQuery })}

I am trying to build a search function into my app and am totally lost. Is there a universal solution yet??
My <Search /> component is very complex.

  • It has to allow for multiple values from different categories.
  • Those get selected (and UNselected) from a <List/>
  • An active Item in that List gets highlighted
  • the - List is updated in realtime

My usage of react-router so far has been reduced to browserHistory.push('/')

@FinnFrotscher maybe this helps..

this.context.history.push({
        ...this.props.location,
        query: Object.assign({}, this.props.location.query, {foo:"bar"})
});

you have to push a new react-router location into the history, you can specify the query params with the query key. The above example will assign foo=bar to the current query. If you would like to remove a param you would need to set it to undefined.. thats it.

I have a history obj in my props but not in my context. Should I use that?

My context is mostly empty.
I only just set up SearchInterface.contextTypes = { router: routerShape.isRequired } and import {browserHistory, routerShape} from 'react-router'

@FinnFrotscher yes, for me it worked with history required by contextTypes

I've end up with these:

import { browserHistory } from 'react-router';

/**
 * @param {Object} query
 */
export const addQuery = (query) => {
    const location = Object.assign({}, browserHistory.getCurrentLocation());
    Object.assign(location.query, query);
    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);
};

And example usage:

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

@DimitryDushkin I am not sure what you pass in as the router param. Could you help give some context?

@jonearley I've updated comment https://github.com/ReactTraining/react-router/issues/1100#issuecomment-272800685 with example

Thank you, just adding this here in case anyone has the same issue with that code snippet: if it's scrolling to top on update, may be due to having react-router-scroll installed (I am new to react and used yeoman generator to create my base project). In my case, don't need that for this project and so uninstalled it.

In v4 I have a simple solution:

this.props.history.push('?tab=summary');

I just found a solution to this problem that doesn't require using browserHistory and seemed somewhat elegant (in ES6):

// To set a parameter:
<Link to={{
    pathname: router.location.pathname,
    query: {
        ...(router.location.query),
        myProp: 'foo',
    }
 }} />

// To unset:
const query = router.location.query;
delete query['myProp'];
/* ... */
<Link to={{
    pathname: router.location.pathname,
    query,
 }} />

// Also, to keep track of whether Link is active
// (query params are not tracked by the Link component):
<Link
    /* ... */
    className={'foo' === query['myProp'] ? 'active' : ''}
/>

This example is modified from my own code so please tell me if it has errors or if it's to terse.

With react-router-4, if you need to append a query param programmatically you can do this:

const query = new URLSearchParams(history.location.search)
query.set('foo', 'bar')
history.replace({...history.location, search: query.toString()})

I can't get these techniques to work in "react-router": "^4.2.0" and "react-router-dom": "^4.2.2".

There is no query to use for the to prop on the Link component anymore, right? I've looked in the docs and in the code.

Am I missing something?

Managed to add query params by simply using "search" property:
<Link to={{pathname: "/some/path", search: props.location.search}}>Title</Link>

Was this page helpful?
0 / 5 - 0 ratings

Related issues

alexyaseen picture alexyaseen  路  3Comments

ArthurRougier picture ArthurRougier  路  3Comments

tomatau picture tomatau  路  3Comments

ryansobol picture ryansobol  路  3Comments

winkler1 picture winkler1  路  3Comments