New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

No more parsing of query strings in V4? #4410

Closed
willemx opened this Issue Jan 31, 2017 · 56 comments

Comments

Projects
None yet
@willemx

willemx commented Jan 31, 2017

I just upgraded from V4 alpha to beta (hooray!), but I can't find the location.query object anymore, only a location.search string. Have I missed something, or do I have to do my own parsing now?

@jonrimmer

This comment has been minimized.

Show comment
Hide comment
@jonrimmer

jonrimmer Jan 31, 2017

I have just discovered the same thing, and was rather surprised. I understand that the ?a=x&b=y query format isn't part of the URL standard de–jure, but it has been a de–facto standard for twenty years, and is how most almost every app handles supplying non-hierarchical params. If React Router's goal is to handle syncing the URL with the UI, then it seems like a strange omission to leave this completely up to the user.

I've seen it mentioned that history could be modified to add query parsing support back in, but without any suggestion how. Seems like an example be useful here, at the very least. Particularly since this is a regression in v4 compared to earlier versions.

jonrimmer commented Jan 31, 2017

I have just discovered the same thing, and was rather surprised. I understand that the ?a=x&b=y query format isn't part of the URL standard de–jure, but it has been a de–facto standard for twenty years, and is how most almost every app handles supplying non-hierarchical params. If React Router's goal is to handle syncing the URL with the UI, then it seems like a strange omission to leave this completely up to the user.

I've seen it mentioned that history could be modified to add query parsing support back in, but without any suggestion how. Seems like an example be useful here, at the very least. Particularly since this is a regression in v4 compared to earlier versions.

@pshrmn

This comment has been minimized.

Show comment
Hide comment
@pshrmn

pshrmn Jan 31, 2017

Collaborator

There are a number of popular packages that do query string parsing/stringifying slightly differently, and each of these differences might be the "correct" way for some users and "incorrect" for others. If React Router picked the "right" one, it would only be right for some people. Then, it would need to add a way for other users to substitute in their preferred query parsing package. There is no internal use of the search string by React Router that requires it to parse the key-value pairs, so it doesn't have a need to pick which one of these should be "right".

I don't know of a great way to add automatic query parsing to the history instance, but I think that you could listen for location changes and modify the history.location object to add the query property. I obviously haven't tested it, but it should work.

Having included that, I do think that it would just make more sense to just parse location.search in your view components that are expecting a query object.

Edit I removed the sample code since people are pointing out that it had issues. It was just something that I wrote in a minute or two without actually testing, so I don't want more people to keep running into problems because of it. The qhistory package that I link to below should be a better example since it is actually tested.

Collaborator

pshrmn commented Jan 31, 2017

There are a number of popular packages that do query string parsing/stringifying slightly differently, and each of these differences might be the "correct" way for some users and "incorrect" for others. If React Router picked the "right" one, it would only be right for some people. Then, it would need to add a way for other users to substitute in their preferred query parsing package. There is no internal use of the search string by React Router that requires it to parse the key-value pairs, so it doesn't have a need to pick which one of these should be "right".

I don't know of a great way to add automatic query parsing to the history instance, but I think that you could listen for location changes and modify the history.location object to add the query property. I obviously haven't tested it, but it should work.

Having included that, I do think that it would just make more sense to just parse location.search in your view components that are expecting a query object.

Edit I removed the sample code since people are pointing out that it had issues. It was just something that I wrote in a minute or two without actually testing, so I don't want more people to keep running into problems because of it. The qhistory package that I link to below should be a better example since it is actually tested.

@mjackson

This comment has been minimized.

Show comment
Hide comment
@mjackson

mjackson Jan 31, 2017

Member

Solid answer, thank you @pshrmn :)

Member

mjackson commented Jan 31, 2017

Solid answer, thank you @pshrmn :)

@mjackson mjackson closed this Jan 31, 2017

@jonrimmer

This comment has been minimized.

Show comment
Hide comment
@jonrimmer

jonrimmer Jan 31, 2017

Sorry, but I just do not follow the logic here. Path params and query string params are two equally valid and popular ways of supplying data through the URL. If the purpose of the library is to support syncing the URL to the UI, then why is parsing and binding one type of param to the match object considered in scope, and the other not? Why only solve half the problem?

Nor do I see any reason why a user would need to supply a particular query string parsing library, any more than they would need to override the router's current path parsing algorithm. They would simply need to use the query string in the way that the router expected, just as they currently have to use the path in the way the router expects.

jonrimmer commented Jan 31, 2017

Sorry, but I just do not follow the logic here. Path params and query string params are two equally valid and popular ways of supplying data through the URL. If the purpose of the library is to support syncing the URL to the UI, then why is parsing and binding one type of param to the match object considered in scope, and the other not? Why only solve half the problem?

Nor do I see any reason why a user would need to supply a particular query string parsing library, any more than they would need to override the router's current path parsing algorithm. They would simply need to use the query string in the way that the router expected, just as they currently have to use the path in the way the router expects.

@mjackson

This comment has been minimized.

Show comment
Hide comment
@mjackson

mjackson Jan 31, 2017

Member

@jonrimmer We've had many, many requests over the years to modify the way we handle query string parsing and serialization. Just had another one (for v3) this morning, in fact! Just because you don't see it, doesn't mean people don't have different needs.

Also, it's incredibly easy for you to provide your own solution here.

import stringifyQuery from 'whatever-lib-you-want'

<Link to={{ pathname: '/the/path', search: stringifyQuery({ your: 'query' }) }}>click me</Link>

Still too much work?

const QueryLink = (props) => (
  <Link {...props} to={{ ...props.to, search: stringifyQuery(props.to.query) }}/>
)

Now you can

<QueryLink to={{ pathname: '/the/path', query: { go: 'nuts' } }}>click me</QueryLink>
Member

mjackson commented Jan 31, 2017

@jonrimmer We've had many, many requests over the years to modify the way we handle query string parsing and serialization. Just had another one (for v3) this morning, in fact! Just because you don't see it, doesn't mean people don't have different needs.

Also, it's incredibly easy for you to provide your own solution here.

import stringifyQuery from 'whatever-lib-you-want'

<Link to={{ pathname: '/the/path', search: stringifyQuery({ your: 'query' }) }}>click me</Link>

Still too much work?

const QueryLink = (props) => (
  <Link {...props} to={{ ...props.to, search: stringifyQuery(props.to.query) }}/>
)

Now you can

<QueryLink to={{ pathname: '/the/path', query: { go: 'nuts' } }}>click me</QueryLink>
@jochenberger

This comment has been minimized.

Show comment
Hide comment
@jochenberger

jochenberger Feb 1, 2017

Contributor

One thing that's become cumbersome in v4 is creating links with some query parameters updated. With v3, I used to do

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

That means that all other potential query parameters stay in place.
I haven't found an equally simple way to do that in v4.

Contributor

jochenberger commented Feb 1, 2017

One thing that's become cumbersome in v4 is creating links with some query parameters updated. With v3, I used to do

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

That means that all other potential query parameters stay in place.
I haven't found an equally simple way to do that in v4.

@mjackson

This comment has been minimized.

Show comment
Hide comment
@mjackson

mjackson Feb 1, 2017

Member

@jochenberger It's literally just

this.props.push({
  pathname: this.props.location.pathname,
  search: stringifyQuery(Object.assign({}, parseQueryString(this.props.location.search), { foo: "bar" }))
});

And if that's still too much work, you can wrap it up in a component as I demonstrated in #4410 (comment)

Member

mjackson commented Feb 1, 2017

@jochenberger It's literally just

this.props.push({
  pathname: this.props.location.pathname,
  search: stringifyQuery(Object.assign({}, parseQueryString(this.props.location.search), { foo: "bar" }))
});

And if that's still too much work, you can wrap it up in a component as I demonstrated in #4410 (comment)

@jochenberger

This comment has been minimized.

Show comment
Hide comment
@jochenberger

jochenberger Feb 1, 2017

Contributor

Thanks, I ended up writing a helper function doing about that. Of course, doing it like this means that you have to parse location.search for every link, but that can be solved by a HOC that parses location.search and passes the result as location.query or passes individual query parameters as props (this is what I did).
I guess that sooner or later there's going to be third-party modules doing just that.

Contributor

jochenberger commented Feb 1, 2017

Thanks, I ended up writing a helper function doing about that. Of course, doing it like this means that you have to parse location.search for every link, but that can be solved by a HOC that parses location.search and passes the result as location.query or passes individual query parameters as props (this is what I did).
I guess that sooner or later there's going to be third-party modules doing just that.

@rubencodes

This comment has been minimized.

Show comment
Hide comment
@rubencodes

rubencodes Feb 2, 2017

I don't mean to beat a dead horse, but this is kind of a bummer 😕 I'd prefer an opinionated solution over no built in solution. The way v3 handled it was good. Ah well, for now looks like the query-string library mentioned above is all I really need.

Thanks all for the hard work on v4! It would be good to document this behavior for future adopters, query strings being as popular a feature as they are—had to dig a bit to find this thread.

rubencodes commented Feb 2, 2017

I don't mean to beat a dead horse, but this is kind of a bummer 😕 I'd prefer an opinionated solution over no built in solution. The way v3 handled it was good. Ah well, for now looks like the query-string library mentioned above is all I really need.

Thanks all for the hard work on v4! It would be good to document this behavior for future adopters, query strings being as popular a feature as they are—had to dig a bit to find this thread.

@timdorr

This comment has been minimized.

Show comment
Hide comment
@timdorr

timdorr Feb 2, 2017

Collaborator

@rubencodes The approach being taken for 4.0 is to strip out all the "batteries included" kind of features and get back to just basic routing. If you need query string parsing or async loading or Redux integration or something else very specific, then you can add that in with a library specifically for your use case. Less cruft is packed in that you don't need and you can customize things to your specific preferences and needs.

Collaborator

timdorr commented Feb 2, 2017

@rubencodes The approach being taken for 4.0 is to strip out all the "batteries included" kind of features and get back to just basic routing. If you need query string parsing or async loading or Redux integration or something else very specific, then you can add that in with a library specifically for your use case. Less cruft is packed in that you don't need and you can customize things to your specific preferences and needs.

@Noitidart

This comment has been minimized.

Show comment
Hide comment
@Noitidart

Noitidart Mar 1, 2017

Contributor

This is a very important topic. Most routing libs handle query params. I fully understand the reasons against it in v4 and totally support it.

For those coming from those other libs, and especially v3 - it might be worth it to mention this whole topic (or at least #4410 (comment) and #4410 (comment) )in the docs though. I searched for this for two days (I am horrible at searching, first place I looked was docs)

This comment here in this topic was also very very helpful to me to understanding v4 methodology - #4527 (comment)

The other thing I struggled with was picking the query string lib. I found this great topic that told the difference between qs and query-string node modules - http://stackoverflow.com/q/29136374/1828637

Contributor

Noitidart commented Mar 1, 2017

This is a very important topic. Most routing libs handle query params. I fully understand the reasons against it in v4 and totally support it.

For those coming from those other libs, and especially v3 - it might be worth it to mention this whole topic (or at least #4410 (comment) and #4410 (comment) )in the docs though. I searched for this for two days (I am horrible at searching, first place I looked was docs)

This comment here in this topic was also very very helpful to me to understanding v4 methodology - #4527 (comment)

The other thing I struggled with was picking the query string lib. I found this great topic that told the difference between qs and query-string node modules - http://stackoverflow.com/q/29136374/1828637

@donaldpipowitch

This comment has been minimized.

Show comment
Hide comment
@donaldpipowitch

donaldpipowitch Mar 1, 2017

FYI: For a lot of cases URLSearchParams is good enough.

const search = props.location.search; // could be '?foo=bar'
const params = new URLSearchParams(search);
const foo = params.get('foo'); // bar

We use the https://github.com/jerrybendy/url-search-params-polyfill for older browsers which works great with webpack.

donaldpipowitch commented Mar 1, 2017

FYI: For a lot of cases URLSearchParams is good enough.

const search = props.location.search; // could be '?foo=bar'
const params = new URLSearchParams(search);
const foo = params.get('foo'); // bar

We use the https://github.com/jerrybendy/url-search-params-polyfill for older browsers which works great with webpack.

@tkrotoff

This comment has been minimized.

Show comment
Hide comment
@tkrotoff

tkrotoff Mar 15, 2017

Ok for not including query string parsing inside React Router but since this is a very common use case, it should be documented here.

tkrotoff commented Mar 15, 2017

Ok for not including query string parsing inside React Router but since this is a very common use case, it should be documented here.

@bkniffler

This comment has been minimized.

Show comment
Hide comment
@bkniffler

bkniffler Mar 17, 2017

I'm really missing the query, and I think many other users expect this to be a core functionality in a router. Currently I see only two suboptimal possibilities, either like @donaldpipowitch, getting the query via URLSearchParams and setting link search by stringifying query on each of your Links (pretty inefficient!) or overriding router components (e.g. Link, withRouter), something like:

export const Link = (props) => {
  if (props.to && props.to.query) {
    props.to.search = stringifyQuery(props.to.query);
    delete props.to.query;
  }
  return <LinkLegacy {...props} />;
};

export const NavLink = (props) => {
  if (props.to && props.to.query) {
    props.to.search = stringifyQuery(props.to.query);
    delete props.to.query;
  }
  return <NavLinkLegacy {...props} />;
};

export const withRouter = (WrappedComponent) => {
  @withRouterLegacy
  class WithRouter extends Component {
    static contextTypes = {
      router: PropTypes.shape({
        history: PropTypes.shape({
          push: PropTypes.func.isRequired,
          replace: PropTypes.func.isRequired,
          createHref: PropTypes.func.isRequired
        }).isRequired
      }).isRequired,
    };
    push = (propsTo) => {
      const to = { ...propsTo };
      if (to.query) {
        to.search = stringifyQuery(to.query);
        delete to.query;
      }
      this.context.router.history.push(to);
    }
    replace = (propsTo) => {
      const to = { ...propsTo };
      if (to.query) {
        to.search = stringifyQuery(to.query);
        delete to.query;
      }
      this.context.router.history.replace(to);
    }
    render() {
      const { location } = this.props;
      location.query = parseQuery(location.search);
      return (
        <WrappedComponent {...this.props} router={{ ...this.context.router, push: this.push, replace: this.replace }} />
      );
    }
  }
  return WithRouter;
};

I don't find either satisfying. Why not give a possibility to define parseQuery/stringifyQuery on the wrapper components (BrowserRouter, StaticRouter, ..) and use these instead of stripping query completely @timdorr ?

bkniffler commented Mar 17, 2017

I'm really missing the query, and I think many other users expect this to be a core functionality in a router. Currently I see only two suboptimal possibilities, either like @donaldpipowitch, getting the query via URLSearchParams and setting link search by stringifying query on each of your Links (pretty inefficient!) or overriding router components (e.g. Link, withRouter), something like:

export const Link = (props) => {
  if (props.to && props.to.query) {
    props.to.search = stringifyQuery(props.to.query);
    delete props.to.query;
  }
  return <LinkLegacy {...props} />;
};

export const NavLink = (props) => {
  if (props.to && props.to.query) {
    props.to.search = stringifyQuery(props.to.query);
    delete props.to.query;
  }
  return <NavLinkLegacy {...props} />;
};

export const withRouter = (WrappedComponent) => {
  @withRouterLegacy
  class WithRouter extends Component {
    static contextTypes = {
      router: PropTypes.shape({
        history: PropTypes.shape({
          push: PropTypes.func.isRequired,
          replace: PropTypes.func.isRequired,
          createHref: PropTypes.func.isRequired
        }).isRequired
      }).isRequired,
    };
    push = (propsTo) => {
      const to = { ...propsTo };
      if (to.query) {
        to.search = stringifyQuery(to.query);
        delete to.query;
      }
      this.context.router.history.push(to);
    }
    replace = (propsTo) => {
      const to = { ...propsTo };
      if (to.query) {
        to.search = stringifyQuery(to.query);
        delete to.query;
      }
      this.context.router.history.replace(to);
    }
    render() {
      const { location } = this.props;
      location.query = parseQuery(location.search);
      return (
        <WrappedComponent {...this.props} router={{ ...this.context.router, push: this.push, replace: this.replace }} />
      );
    }
  }
  return WithRouter;
};

I don't find either satisfying. Why not give a possibility to define parseQuery/stringifyQuery on the wrapper components (BrowserRouter, StaticRouter, ..) and use these instead of stripping query completely @timdorr ?

@tkrotoff

This comment has been minimized.

Show comment
Hide comment
@tkrotoff

tkrotoff Mar 17, 2017

And what about having another package like react-router-extra that includes location.query, LinkQuery... and other common use cases (in an opinionated manner) without polluting core?

tkrotoff commented Mar 17, 2017

And what about having another package like react-router-extra that includes location.query, LinkQuery... and other common use cases (in an opinionated manner) without polluting core?

@pshrmn

This comment has been minimized.

Show comment
Hide comment
@pshrmn

pshrmn Mar 17, 2017

Collaborator

@tkrotoff You can try qhistory.

Collaborator

pshrmn commented Mar 17, 2017

@tkrotoff You can try qhistory.

@DORRITO

This comment has been minimized.

Show comment
Hide comment
@DORRITO

DORRITO Apr 2, 2017

Ty Donald for that post. Adding a late comment for anyone else searching this post for how to update their code, and they aren't sure what package to grab, 'query-string' is simple one, and worked for me after searching some recommendations on stack overflow.

I love React, and so far love all the v4 changes. in this however, complete opinion: The team made an overthinking move to get rid of this awesome and simple feature. I love that v4's goal is to simplify, so this thinking does the opposite, just to satisfy the loud minority. Sends the signal that if we send them enough emails they will change things to how we want them.

I fully understand it, but I'd say the quiet majority of us who haven't been emailing are against any 'upgrades' that make us add lines to our code. Despite that, I still appreciate all the hard work the team does! I feel I have no room to complain because the team made it in the first place :)

DORRITO commented Apr 2, 2017

Ty Donald for that post. Adding a late comment for anyone else searching this post for how to update their code, and they aren't sure what package to grab, 'query-string' is simple one, and worked for me after searching some recommendations on stack overflow.

I love React, and so far love all the v4 changes. in this however, complete opinion: The team made an overthinking move to get rid of this awesome and simple feature. I love that v4's goal is to simplify, so this thinking does the opposite, just to satisfy the loud minority. Sends the signal that if we send them enough emails they will change things to how we want them.

I fully understand it, but I'd say the quiet majority of us who haven't been emailing are against any 'upgrades' that make us add lines to our code. Despite that, I still appreciate all the hard work the team does! I feel I have no room to complain because the team made it in the first place :)

@lcoder

This comment has been minimized.

Show comment
Hide comment
@lcoder

lcoder Apr 13, 2017

@pshrmn
This has a bug

const history = createBrowserHistory()

// register the first listener. These are called synchronously, so
// the next listener won't be called until this has finished
history.listen(() => {
  history.location = Object.assign(history.location,
    // parse the search string using your package of choice
    { query: parseQueryString(history.location.search) }
  )
})

when a user refresh the browser. history.listen doesn't trigger so the location.query === undefined .how to get location.query when get the page at the first visit .

lcoder commented Apr 13, 2017

@pshrmn
This has a bug

const history = createBrowserHistory()

// register the first listener. These are called synchronously, so
// the next listener won't be called until this has finished
history.listen(() => {
  history.location = Object.assign(history.location,
    // parse the search string using your package of choice
    { query: parseQueryString(history.location.search) }
  )
})

when a user refresh the browser. history.listen doesn't trigger so the location.query === undefined .how to get location.query when get the page at the first visit .

@pantharshit00

This comment has been minimized.

Show comment
Hide comment
@pantharshit00

pantharshit00 Apr 22, 2017

You can use this to decode query String

const query = decodeURIComponent(this.props.location.search.replace(new RegExp("^(?:.*[&\\?]" + encodeURIComponent("redirect"/* Query want to decode*/).replace(/[\.\+\*]/g, "\\$&") + "(?:\\=([^&]*))?)?.*$", "i"), "$1")

pantharshit00 commented Apr 22, 2017

You can use this to decode query String

const query = decodeURIComponent(this.props.location.search.replace(new RegExp("^(?:.*[&\\?]" + encodeURIComponent("redirect"/* Query want to decode*/).replace(/[\.\+\*]/g, "\\$&") + "(?:\\=([^&]*))?)?.*$", "i"), "$1")
@v1kku

This comment has been minimized.

Show comment
Hide comment
@v1kku

v1kku Apr 23, 2017

@lcoder Add the query outside the listener.

function addLocationQuery(history){
    history.location = Object.assign(
        history.location,
        {
            query: parseQueryString(history.location.search)
        }
    )
}

addLocationQuery(history);

history.listen(() => {
    addLocationQuery(history)
})

v1kku commented Apr 23, 2017

@lcoder Add the query outside the listener.

function addLocationQuery(history){
    history.location = Object.assign(
        history.location,
        {
            query: parseQueryString(history.location.search)
        }
    )
}

addLocationQuery(history);

history.listen(() => {
    addLocationQuery(history)
})
@lcoder

This comment has been minimized.

Show comment
Hide comment
@lcoder

lcoder commented Apr 24, 2017

@v1kku Thanks!

@Aiky30

This comment has been minimized.

Show comment
Hide comment
@Aiky30

Aiky30 May 3, 2017

I personally believe that it is a poor decision to leave this out. Now everyone has to bake there own solution rather than the picky few where the default doesn't work / fit, they (picky few) would have previously been the only ones who would need to bake their own solution.

Now I've been following react router v4 for a while now (and the project from v1) and it's taken so many huge steps which in some cases have been reversed. One of the most difficult projects from an early stage that I've ever followed. I understand it's not stable etc but how are people supposed to contribute in such a situation. I respect the work of people here, I just find it very frustrating that changes are made without any justification or even documentation / notes that parts have changed / been removed. I find myself trawling issues to find that parts have been removed or are deprecated!!!

Aiky30 commented May 3, 2017

I personally believe that it is a poor decision to leave this out. Now everyone has to bake there own solution rather than the picky few where the default doesn't work / fit, they (picky few) would have previously been the only ones who would need to bake their own solution.

Now I've been following react router v4 for a while now (and the project from v1) and it's taken so many huge steps which in some cases have been reversed. One of the most difficult projects from an early stage that I've ever followed. I understand it's not stable etc but how are people supposed to contribute in such a situation. I respect the work of people here, I just find it very frustrating that changes are made without any justification or even documentation / notes that parts have changed / been removed. I find myself trawling issues to find that parts have been removed or are deprecated!!!

@aequasi

This comment has been minimized.

Show comment
Hide comment
@aequasi

aequasi May 5, 2017

Could not agree more. Removing this feature without giving an alternative, or even documenting it is a huge oversight.

Also, the "correct" way of doing it could be solved by allowing users to pick their library. Similar to how mongoose selects their promise library.

aequasi commented May 5, 2017

Could not agree more. Removing this feature without giving an alternative, or even documenting it is a huge oversight.

Also, the "correct" way of doing it could be solved by allowing users to pick their library. Similar to how mongoose selects their promise library.

@aludvigsen

This comment has been minimized.

Show comment
Hide comment
@aludvigsen

aludvigsen May 8, 2017

I would assume parsing querystrings is a fairly common task, and it would be great if the docs said something about best practices for using/including a parsing library 👍

aludvigsen commented May 8, 2017

I would assume parsing querystrings is a fairly common task, and it would be great if the docs said something about best practices for using/including a parsing library 👍

@odigity

This comment has been minimized.

Show comment
Hide comment
@odigity

odigity May 13, 2017

The bug that @lcoder pointed out in @pshrmn's code is exactly why core features should be added to the codebase, not offered casually in an issue comment.

I understand minimalism, but it's a URL routing library. The querystring is a core part of the URL. I don't want to be a dick - I love react-router and appreciate the work you guys do - but I just can't comprehend or accept this decision.

odigity commented May 13, 2017

The bug that @lcoder pointed out in @pshrmn's code is exactly why core features should be added to the codebase, not offered casually in an issue comment.

I understand minimalism, but it's a URL routing library. The querystring is a core part of the URL. I don't want to be a dick - I love react-router and appreciate the work you guys do - but I just can't comprehend or accept this decision.

@bitcoinporn

This comment has been minimized.

Show comment
Hide comment
@bitcoinporn

bitcoinporn May 13, 2017

Just landed on this page after 1 hour trying figure out why query not working anymore.

I completely don't understand why should remove query on parameters.
Because of this, for simple url manipulation we need extra code.

bitcoinporn commented May 13, 2017

Just landed on this page after 1 hour trying figure out why query not working anymore.

I completely don't understand why should remove query on parameters.
Because of this, for simple url manipulation we need extra code.

@odigity

This comment has been minimized.

Show comment
Hide comment
@odigity

odigity May 13, 2017

Addendum: I couldn't get the pshrmn's example working. I add tried adding a query property to the location object in the listen handler, but the change wasn't visible from my component. I also tried retaining a reference to a custom history object (which was handed to the Router) and setting it via history.location.query, but that didn't work, either.

I've since given up. I refuse to parse querystrings in every single component that wants to access them, and am just hoping someone else solves the problem properly soon, either in the core or with a v4-compatible extension.

odigity commented May 13, 2017

Addendum: I couldn't get the pshrmn's example working. I add tried adding a query property to the location object in the listen handler, but the change wasn't visible from my component. I also tried retaining a reference to a custom history object (which was handed to the Router) and setting it via history.location.query, but that didn't work, either.

I've since given up. I refuse to parse querystrings in every single component that wants to access them, and am just hoping someone else solves the problem properly soon, either in the core or with a v4-compatible extension.

@khankuan

This comment has been minimized.

Show comment
Hide comment
@khankuan

khankuan May 15, 2017

I'm trying v4 for the first time and faced the same issue as well. Routing is definitely a hard problem in the web. In next.js, I had to install 2 other packages just to get basic coverage for typical use cases.

Here is my patch so far:

// utils/react-router-patch.js
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import pathToRegexp from 'path-to-regexp'
import qs from 'qs';
import {
  Route as ReactRouterRoute,
  Link as ReactRouterLink,
} from 'react-router-dom';

export class Route extends Component {

  static displayName = 'PatchedRoute';

  static propTypes = {
    location: ReactRouterRoute.propTypes.location,
  }

  static contextTypes = {
    router: ReactRouterRoute.contextTypes.router,
  }

  render() {
    let location = this.props.location || this.context.router.route.location;
    if (location && location.search && location.search.length > 1) {
      location = { ...location };
      location.query = qs.parse(location.search.substring(1));
    }
    return <ReactRouterRoute {...this.props} location={location} />
  }
}

export class Link extends Component {

  static displayName = 'PatchedLink';

  static propTypes = {
    path: PropTypes.string, //  Route's path
    params: PropTypes.object,
    query: PropTypes.object,
    hash: PropTypes.string,
  }

  render() {
    let to = this.props.to;
    const { path, params, query, hash, ...rest } = this.props;
    if (path) {
      to = getRouteHref(path, params, query, hash)
    }
    return <ReactRouterLink {...rest} to={to} />
  }
}

//  Function to get href
//  Example: getRouteHref('/users/:userId', { userId: '123' }, { referrer: 'ads' }, 'section')
export function getRouteHref(path, params, query, hash) {
  const toPathRegexp = pathToRegexp.compile(path);
  let url;
  try {
    url = toPathRegexp(params);
    if (query) {
      url += '?' + qs.stringify(query);
    }
    if (hash) {
      url += '#' + hash;
    }
  } catch (err) {
    console.warn(err);
    url = '#';
  }
  return url;
}

Usage:

const routes = {
  home: {
    path: '/',
    exact: true,
    main: HomePage,
  },
  user: {
    path: '/user/:userId',
    main: UserPage,
  },
}

<Link path={routes.user.path} params={{ userId: '123' }} query={{ referrer: 'fb' }} />

khankuan commented May 15, 2017

I'm trying v4 for the first time and faced the same issue as well. Routing is definitely a hard problem in the web. In next.js, I had to install 2 other packages just to get basic coverage for typical use cases.

Here is my patch so far:

// utils/react-router-patch.js
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import pathToRegexp from 'path-to-regexp'
import qs from 'qs';
import {
  Route as ReactRouterRoute,
  Link as ReactRouterLink,
} from 'react-router-dom';

export class Route extends Component {

  static displayName = 'PatchedRoute';

  static propTypes = {
    location: ReactRouterRoute.propTypes.location,
  }

  static contextTypes = {
    router: ReactRouterRoute.contextTypes.router,
  }

  render() {
    let location = this.props.location || this.context.router.route.location;
    if (location && location.search && location.search.length > 1) {
      location = { ...location };
      location.query = qs.parse(location.search.substring(1));
    }
    return <ReactRouterRoute {...this.props} location={location} />
  }
}

export class Link extends Component {

  static displayName = 'PatchedLink';

  static propTypes = {
    path: PropTypes.string, //  Route's path
    params: PropTypes.object,
    query: PropTypes.object,
    hash: PropTypes.string,
  }

  render() {
    let to = this.props.to;
    const { path, params, query, hash, ...rest } = this.props;
    if (path) {
      to = getRouteHref(path, params, query, hash)
    }
    return <ReactRouterLink {...rest} to={to} />
  }
}

//  Function to get href
//  Example: getRouteHref('/users/:userId', { userId: '123' }, { referrer: 'ads' }, 'section')
export function getRouteHref(path, params, query, hash) {
  const toPathRegexp = pathToRegexp.compile(path);
  let url;
  try {
    url = toPathRegexp(params);
    if (query) {
      url += '?' + qs.stringify(query);
    }
    if (hash) {
      url += '#' + hash;
    }
  } catch (err) {
    console.warn(err);
    url = '#';
  }
  return url;
}

Usage:

const routes = {
  home: {
    path: '/',
    exact: true,
    main: HomePage,
  },
  user: {
    path: '/user/:userId',
    main: UserPage,
  },
}

<Link path={routes.user.path} params={{ userId: '123' }} query={{ referrer: 'fb' }} />
@wdjungst

This comment has been minimized.

Show comment
Hide comment
@wdjungst

wdjungst May 15, 2017

wdjungst commented May 15, 2017

@thebigredgeek

This comment has been minimized.

Show comment
Hide comment
@thebigredgeek

thebigredgeek May 15, 2017

Query parsing is not "batteries", so not sure what is meant when you guys say strip out all the "batteries included" kind of features. It's a core piece of functionality to any legitimate routing solution. This is like shipping a laptop without a trackpad and insisting that the customer purchase a mouse. I guess I'll be waiting for v5 to upgrade :(

thebigredgeek commented May 15, 2017

Query parsing is not "batteries", so not sure what is meant when you guys say strip out all the "batteries included" kind of features. It's a core piece of functionality to any legitimate routing solution. This is like shipping a laptop without a trackpad and insisting that the customer purchase a mouse. I guess I'll be waiting for v5 to upgrade :(

@thekidder

This comment has been minimized.

Show comment
Hide comment
@thekidder

thekidder May 16, 2017

I'm not going to comment on the right- or wrong-ness of this change, but this probably should be mentioned in the migration doc:

https://github.com/ReactTraining/react-router/blob/master/packages/react-router/docs/guides/migrating.md

Discovering this omission up-front would save some frustration rather than halfway through a migration.

thekidder commented May 16, 2017

I'm not going to comment on the right- or wrong-ness of this change, but this probably should be mentioned in the migration doc:

https://github.com/ReactTraining/react-router/blob/master/packages/react-router/docs/guides/migrating.md

Discovering this omission up-front would save some frustration rather than halfway through a migration.

@timdorr

This comment has been minimized.

Show comment
Hide comment
@timdorr

timdorr May 16, 2017

Collaborator

@thekidder The migration doc is a first pass, so we definitely want PRs to improve it.

Collaborator

timdorr commented May 16, 2017

@thekidder The migration doc is a first pass, so we definitely want PRs to improve it.

@pshrmn

This comment has been minimized.

Show comment
Hide comment
@pshrmn

pshrmn May 18, 2017

Collaborator

Just two more thought on this.

First, while this discussion has been in the context of React Router, this issue would really be more appropriate in the history repo (although I'm not proposing to move this discussion over there because the answers will be the same). Someone could fork history, add location.query support, and use that as a drop-in replacement for history with React Router (using the <Router> component). So long as you support the same API, React Router would not care.

Second, I know that some people will never be convinced that built-in support for query objects isn't necessary, so this is for those who are willing to accept that and try to find a solution that works for them 😉

In theory, you should be able to render a "query injector" at the root of your application that modifies the location object by parsing its search string to add a query property. I have a gist of the concept here https://gist.github.com/pshrmn/d0e888dae58856a95c87a80f730d61fc if anyone wants to play around with the idea. That doesn't add support for navigating with query objects, but for that you should still just stringify the object yourself.

Collaborator

pshrmn commented May 18, 2017

Just two more thought on this.

First, while this discussion has been in the context of React Router, this issue would really be more appropriate in the history repo (although I'm not proposing to move this discussion over there because the answers will be the same). Someone could fork history, add location.query support, and use that as a drop-in replacement for history with React Router (using the <Router> component). So long as you support the same API, React Router would not care.

Second, I know that some people will never be convinced that built-in support for query objects isn't necessary, so this is for those who are willing to accept that and try to find a solution that works for them 😉

In theory, you should be able to render a "query injector" at the root of your application that modifies the location object by parsing its search string to add a query property. I have a gist of the concept here https://gist.github.com/pshrmn/d0e888dae58856a95c87a80f730d61fc if anyone wants to play around with the idea. That doesn't add support for navigating with query objects, but for that you should still just stringify the object yourself.

@odigity

This comment has been minimized.

Show comment
Hide comment
@odigity

odigity May 18, 2017

"you should still just stringify the object yourself"

...or find a routing library that's willing to, you know, route.

I'm trying real hard not to be dick, but this is madness. I don't even know what to say at this point.

odigity commented May 18, 2017

"you should still just stringify the object yourself"

...or find a routing library that's willing to, you know, route.

I'm trying real hard not to be dick, but this is madness. I don't even know what to say at this point.

@timdorr

This comment has been minimized.

Show comment
Hide comment
@timdorr

timdorr May 18, 2017

Collaborator

@odigity It does route. Query string parsing is another matter entirely. Again, the are multiple ways to handle it (qs, query_string, and querystring all are different for arrays, for example) and we don't have an opinion about which is "best".

But most of all, this isn't the router's job. It's the underlying history library that caused the change. If you want to effect change, start there.

Collaborator

timdorr commented May 18, 2017

@odigity It does route. Query string parsing is another matter entirely. Again, the are multiple ways to handle it (qs, query_string, and querystring all are different for arrays, for example) and we don't have an opinion about which is "best".

But most of all, this isn't the router's job. It's the underlying history library that caused the change. If you want to effect change, start there.

@dlong500

This comment has been minimized.

Show comment
Hide comment
@dlong500

dlong500 May 18, 2017

Maybe some of the tension could be alleviated if a good set of examples could be put together that shows how all the native features in v3 could be accomplished in v4. Given that in v3 there was "the way" to do something, it seems logical that a set of examples would at least demonstrate "a way" to do the same thing that would handle 95% of use cases. If those examples didn't cover a particular use case then, as has been mentioned numerous times, we could always "roll our own" solution. But most people would be OK with an opinionated way especially if it is basically the way things were handled in v3.

Without meaning to cast any aspersion at all on those who have graciously spent their time working on these modules, I do think it would have been nice if there could have been a react-router-core module acting like react-router v4 does now along with a more full featured react-router (react-router-core + opinionated addons) to ease the transition from v3.

dlong500 commented May 18, 2017

Maybe some of the tension could be alleviated if a good set of examples could be put together that shows how all the native features in v3 could be accomplished in v4. Given that in v3 there was "the way" to do something, it seems logical that a set of examples would at least demonstrate "a way" to do the same thing that would handle 95% of use cases. If those examples didn't cover a particular use case then, as has been mentioned numerous times, we could always "roll our own" solution. But most people would be OK with an opinionated way especially if it is basically the way things were handled in v3.

Without meaning to cast any aspersion at all on those who have graciously spent their time working on these modules, I do think it would have been nice if there could have been a react-router-core module acting like react-router v4 does now along with a more full featured react-router (react-router-core + opinionated addons) to ease the transition from v3.

@timdorr

This comment has been minimized.

Show comment
Hide comment
@timdorr

timdorr May 19, 2017

Collaborator

The history library is responsible for dealing with and parsing locations. That includes query parsing, which we configure it to do in 3.0 (v2/3 history actually doesn't do query parsing out of the box).

As a result of it dropping query parsing, we have to drop it as well. Further complicating matters, our chosen library for pathname, path-to-regexp, doesn't support reading querystrings either:

Please note: The RegExp returned by path-to-regexp is intended for use with pathnames or hostnames. It can not handle the query strings or fragments of a URL.

So, it's a combination of a few factors that ended on the decision of dropping query string parsing:

  1. qs, query-string, and querystring all parse slightly differently. Arrays are a prime example with qs supporting only bracket syntax, querystring only doing duplicate syntax, and query-string supporting 3 different modes.
  2. They're not small libraries and we're trying to be more minimal. qs is 17.7Kb when packed up.
  3. Our matching library doesn't provide support as a fallback.
  4. Even if we do add it, query strings increase the complexity of a number of API surfaces. Do we match when the exact query string is provided, when a subset of the values match, or when all the values are the same but in different order? Or some other definition of "matching"?

I am acutely aware that this is a thing everyone wants, but it has some severe drawbacks and creates a far more opinionated library. We're trying to be simple and generic. That's one of the central theses of 4.0.

If people want this resolved (hell, I want this resolved!), then there are two bits that need to happen:

  1. A wrapper/enhancer for history 4.x needs to be written to add query parsing support. A good way to do this would be to choose a default library (query-string is probably the best) and let people import other builds against different libraries based on the import path. E.g., import createHistory from 'history/qs'.
  2. A wrapper/enhancer/replacement for react-router to add query string handling. Now that you have the query strings parsed how you like, the decision needs to be made for how to match them. Having it be configurable would be good. Or having a few different options that can be imported separately like the history suggestion above would be good.

I know that puts the ball in someone else's court. But that's what open source is all about. If you want to see something changed, don't just pitch a fit, actually write some damn code. We're very open to expanding the packages hosted in this repo, now that we have the monorepo format established. That's why we brought in react-router-redux and react-router-config. We want more! And this is exactly the kind of thing that we want to support. Please help us!

Collaborator

timdorr commented May 19, 2017

The history library is responsible for dealing with and parsing locations. That includes query parsing, which we configure it to do in 3.0 (v2/3 history actually doesn't do query parsing out of the box).

As a result of it dropping query parsing, we have to drop it as well. Further complicating matters, our chosen library for pathname, path-to-regexp, doesn't support reading querystrings either:

Please note: The RegExp returned by path-to-regexp is intended for use with pathnames or hostnames. It can not handle the query strings or fragments of a URL.

So, it's a combination of a few factors that ended on the decision of dropping query string parsing:

  1. qs, query-string, and querystring all parse slightly differently. Arrays are a prime example with qs supporting only bracket syntax, querystring only doing duplicate syntax, and query-string supporting 3 different modes.
  2. They're not small libraries and we're trying to be more minimal. qs is 17.7Kb when packed up.
  3. Our matching library doesn't provide support as a fallback.
  4. Even if we do add it, query strings increase the complexity of a number of API surfaces. Do we match when the exact query string is provided, when a subset of the values match, or when all the values are the same but in different order? Or some other definition of "matching"?

I am acutely aware that this is a thing everyone wants, but it has some severe drawbacks and creates a far more opinionated library. We're trying to be simple and generic. That's one of the central theses of 4.0.

If people want this resolved (hell, I want this resolved!), then there are two bits that need to happen:

  1. A wrapper/enhancer for history 4.x needs to be written to add query parsing support. A good way to do this would be to choose a default library (query-string is probably the best) and let people import other builds against different libraries based on the import path. E.g., import createHistory from 'history/qs'.
  2. A wrapper/enhancer/replacement for react-router to add query string handling. Now that you have the query strings parsed how you like, the decision needs to be made for how to match them. Having it be configurable would be good. Or having a few different options that can be imported separately like the history suggestion above would be good.

I know that puts the ball in someone else's court. But that's what open source is all about. If you want to see something changed, don't just pitch a fit, actually write some damn code. We're very open to expanding the packages hosted in this repo, now that we have the monorepo format established. That's why we brought in react-router-redux and react-router-config. We want more! And this is exactly the kind of thing that we want to support. Please help us!

@odigity

This comment has been minimized.

Show comment
Hide comment
@odigity

odigity May 19, 2017

Matching query strings is complicated and rarely needed, which is probably why the utilities you depend on don't support it.

So don't do that. Just parse the query string portion into an object and make it available in the component, and add support for serializing an object to a query string in the Link component. That's all most people need, and both are relatively straightforward.

If you're concerned about committing to a query string parsing library, and don't want to pick a solution for corner cases (like arrays), and don't want to add dependencies / increase total size... don't. Just give us two hooks for providing our own serialize/deserialize methods and let us supply them once when starting our app. (You can even count on us for creating examples for the popular parser libraries to add to the docs or wiki.)

You know, similar to @locoder's example above, except actually working, which that didn't.

odigity commented May 19, 2017

Matching query strings is complicated and rarely needed, which is probably why the utilities you depend on don't support it.

So don't do that. Just parse the query string portion into an object and make it available in the component, and add support for serializing an object to a query string in the Link component. That's all most people need, and both are relatively straightforward.

If you're concerned about committing to a query string parsing library, and don't want to pick a solution for corner cases (like arrays), and don't want to add dependencies / increase total size... don't. Just give us two hooks for providing our own serialize/deserialize methods and let us supply them once when starting our app. (You can even count on us for creating examples for the popular parser libraries to add to the docs or wiki.)

You know, similar to @locoder's example above, except actually working, which that didn't.

@timdorr

This comment has been minimized.

Show comment
Hide comment
@timdorr

timdorr May 19, 2017

Collaborator

If you want just query string parsing and serialization, that's a history concern. Definitely follow and chime in on ReactTraining/history#478

Collaborator

timdorr commented May 19, 2017

If you want just query string parsing and serialization, that's a history concern. Definitely follow and chime in on ReactTraining/history#478

@odigity

This comment has been minimized.

Show comment
Hide comment
@odigity

odigity May 19, 2017

Thanks @timdorr, will do.

odigity commented May 19, 2017

Thanks @timdorr, will do.

@menelike

This comment has been minimized.

Show comment
Hide comment
@menelike

menelike May 30, 2017

In our case mostly do two things:

  1. Push a new location (sometimes with query params)
  2. Determine which path is currently active

We did not want to use withRouter as it doesn't play well with PureComponents, so I wanted to share our approach/POC as it might help others:

/* eslint-disable react/no-multi-comp, react/prefer-stateless-function */
import React from 'react';
import PropTypes from 'prop-types';
import queryString from 'query-string';

export class Push extends React.Component {
  static propTypes = {
    path: PropTypes.string,
    query: PropTypes.object,
    children: PropTypes.node.isRequired,
  };

  static contextTypes = {
    router: PropTypes.shape({
      history: PropTypes.object.isRequired,
      route: PropTypes.shape({
        location: PropTypes.shape({
          pathname: PropTypes.string.isRequired,
        }).isRequired,
      }).isRequired,
    }).isRequired,
  };

  handlePush = e => {
    if (e && e.preventDefault) e.preventDefault();
    const { path, query } = this.props;
    const { router: { history: { push }, route: { location: { pathname } } } } = this.context;

    let nextPath = pathname;
    if (path) {
      if (path.startsWith('/')) nextPath = path;
      else nextPath = pathname.endsWith('/') ? `${pathname}${path}` : `${pathname}/${path}`;
    }

    if (query) push({ pathname: nextPath, search: queryString.stringify(query) });
    else push(nextPath);
  };

  render() {
    const child = React.Children.only(this.props.children);
    return React.cloneElement(child, { onTouchTap: this.handlePush });
  }
}

export const withLocation = DecoratedComponent => class WithLocation extends React.Component {
  static contextTypes = {
    router: PropTypes.shape({
      route: PropTypes.shape({
        location: PropTypes.shape({
          pathname: PropTypes.string.isRequired,
          search: PropTypes.string.isRequired,
        }).isRequired,
      }).isRequired,
    }).isRequired,
  };

  constructor(props, context) {
    super(props, context);
    const { router: { route: { location: { search } } } } = this.context;

    this.state = {
      query: queryString.parse(search),
    };
  }

  componentWillReceiveProps(nextProps, { router: { route: { location: { search } } } }) {
    if (search !== this.context.router.route.location.search) this.setState({ query: queryString.parse(search) });
  }

  render() {
    const { router: { route: { location: { pathname, search } } } } = this.context;
    const { query } = this.state;

    return (
      <DecoratedComponent
        {...this.props}
        pathname={pathname}
        search={search}
        query={query}
      />
    );
  }
};

Get the path, search and query

@withLocation
export default class ParentComponent extends React.PureComponent {

and inject onTouchTap into the child component

        <Push query={{ filter: 'all' }}>
          <ChildButtonComponent
            active={query.filter === 'all'}
          />
        </Push>
        <Push path="/some/absolute/path/" query={{ filter: 'foo' }}>
          <ChildButtonComponent
            active={query.filter === 'foo'}
          />
        </Push>
        <Push path="some/relative/path" query={{ filter: 'bar' }}>
          <ChildButtonComponent
            active={query.filter === 'bar'}
          />
        </Push>

Coming from ReactRouterV2 we also try to dodge the next API change ;) and tried to decouple the router context from our components.

menelike commented May 30, 2017

In our case mostly do two things:

  1. Push a new location (sometimes with query params)
  2. Determine which path is currently active

We did not want to use withRouter as it doesn't play well with PureComponents, so I wanted to share our approach/POC as it might help others:

/* eslint-disable react/no-multi-comp, react/prefer-stateless-function */
import React from 'react';
import PropTypes from 'prop-types';
import queryString from 'query-string';

export class Push extends React.Component {
  static propTypes = {
    path: PropTypes.string,
    query: PropTypes.object,
    children: PropTypes.node.isRequired,
  };

  static contextTypes = {
    router: PropTypes.shape({
      history: PropTypes.object.isRequired,
      route: PropTypes.shape({
        location: PropTypes.shape({
          pathname: PropTypes.string.isRequired,
        }).isRequired,
      }).isRequired,
    }).isRequired,
  };

  handlePush = e => {
    if (e && e.preventDefault) e.preventDefault();
    const { path, query } = this.props;
    const { router: { history: { push }, route: { location: { pathname } } } } = this.context;

    let nextPath = pathname;
    if (path) {
      if (path.startsWith('/')) nextPath = path;
      else nextPath = pathname.endsWith('/') ? `${pathname}${path}` : `${pathname}/${path}`;
    }

    if (query) push({ pathname: nextPath, search: queryString.stringify(query) });
    else push(nextPath);
  };

  render() {
    const child = React.Children.only(this.props.children);
    return React.cloneElement(child, { onTouchTap: this.handlePush });
  }
}

export const withLocation = DecoratedComponent => class WithLocation extends React.Component {
  static contextTypes = {
    router: PropTypes.shape({
      route: PropTypes.shape({
        location: PropTypes.shape({
          pathname: PropTypes.string.isRequired,
          search: PropTypes.string.isRequired,
        }).isRequired,
      }).isRequired,
    }).isRequired,
  };

  constructor(props, context) {
    super(props, context);
    const { router: { route: { location: { search } } } } = this.context;

    this.state = {
      query: queryString.parse(search),
    };
  }

  componentWillReceiveProps(nextProps, { router: { route: { location: { search } } } }) {
    if (search !== this.context.router.route.location.search) this.setState({ query: queryString.parse(search) });
  }

  render() {
    const { router: { route: { location: { pathname, search } } } } = this.context;
    const { query } = this.state;

    return (
      <DecoratedComponent
        {...this.props}
        pathname={pathname}
        search={search}
        query={query}
      />
    );
  }
};

Get the path, search and query

@withLocation
export default class ParentComponent extends React.PureComponent {

and inject onTouchTap into the child component

        <Push query={{ filter: 'all' }}>
          <ChildButtonComponent
            active={query.filter === 'all'}
          />
        </Push>
        <Push path="/some/absolute/path/" query={{ filter: 'foo' }}>
          <ChildButtonComponent
            active={query.filter === 'foo'}
          />
        </Push>
        <Push path="some/relative/path" query={{ filter: 'bar' }}>
          <ChildButtonComponent
            active={query.filter === 'bar'}
          />
        </Push>

Coming from ReactRouterV2 we also try to dodge the next API change ;) and tried to decouple the router context from our components.

@Ethan-Arrowood

This comment has been minimized.

Show comment
Hide comment
@Ethan-Arrowood

Ethan-Arrowood Jun 11, 2017

I understand the 'there is no right way argument' but I think there should at least be a note about this in the documentation. I spent too long scratching my head over this and it wasn't until I finally found this issue thread was I able to figure it out.

Something as simple as:
In React Router v.4 We are no longer supporting URL String querying. We recommend you use an independent npm package such as . . . (query-string worked great for me).

Ethan-Arrowood commented Jun 11, 2017

I understand the 'there is no right way argument' but I think there should at least be a note about this in the documentation. I spent too long scratching my head over this and it wasn't until I finally found this issue thread was I able to figure it out.

Something as simple as:
In React Router v.4 We are no longer supporting URL String querying. We recommend you use an independent npm package such as . . . (query-string worked great for me).

@maxlapshin

This comment has been minimized.

Show comment
Hide comment
@maxlapshin

maxlapshin Jun 12, 2017

All this looks very strange for me, because reading query string is only a part of routing.

Right now there will be giant amount of boilerplate code that replaces params in query string with new values and I don't see any documentation or hints for history: How to push new history when I change query string from code?

maxlapshin commented Jun 12, 2017

All this looks very strange for me, because reading query string is only a part of routing.

Right now there will be giant amount of boilerplate code that replaces params in query string with new values and I don't see any documentation or hints for history: How to push new history when I change query string from code?

@codeaid

This comment has been minimized.

Show comment
Hide comment
@codeaid

codeaid Jun 13, 2017

I've seen some people here saying that query string is not part of routing and that the reason it was removed is that it's not part of the standard (who cares that we've been using them for like 20+ years).

I completely disagree with that. In a normal application a page on /users?page=2 is not the same as /users?page=5 and at the moment router simply strips this vital information from the URL and considers that nothing changed when I navigate from one page to another.

I can live with query strings not being parsed by using URLSearchParams or query-string or some library X, but I can't live with router not picking up on URL changes. I'm slowly getting tired of waking up each day and finding that a library that I use has been completely rewritten and, even worse, features that until now were a norm are suddenly removed.

There, even though I know this is not Stack Overflow, I'd like to ask a question seeing how there's no documentation about this problem whatsoever - is there a way to force component to rerender on a query string change? I have pagination Link components with to={{ pathname, search }} attributes specified on them. The links get generated correctly but of course when I click on any of them nothing happens and I can't for the love of God find any solutions...

codeaid commented Jun 13, 2017

I've seen some people here saying that query string is not part of routing and that the reason it was removed is that it's not part of the standard (who cares that we've been using them for like 20+ years).

I completely disagree with that. In a normal application a page on /users?page=2 is not the same as /users?page=5 and at the moment router simply strips this vital information from the URL and considers that nothing changed when I navigate from one page to another.

I can live with query strings not being parsed by using URLSearchParams or query-string or some library X, but I can't live with router not picking up on URL changes. I'm slowly getting tired of waking up each day and finding that a library that I use has been completely rewritten and, even worse, features that until now were a norm are suddenly removed.

There, even though I know this is not Stack Overflow, I'd like to ask a question seeing how there's no documentation about this problem whatsoever - is there a way to force component to rerender on a query string change? I have pagination Link components with to={{ pathname, search }} attributes specified on them. The links get generated correctly but of course when I click on any of them nothing happens and I can't for the love of God find any solutions...

@pshrmn

This comment has been minimized.

Show comment
Hide comment
@pshrmn

pshrmn Jun 13, 2017

Collaborator

@codeaid When the pathname stays the same, but the search string changes, you will still be matching with the same <Route>. This means that instead of the <Route> mounting a new component, it will simply update the existing one. That means that you will need to add update lifecycle methods to the component to get the new search string and fetch the data for it.

The below code should illustrate the idea:

// <Route path='/users' component={Users} />
class Users extends React.Component {
  componentWillMount() {
    this.fetchUsers(this.props.location.search);
  }

  componentWillUpdate(nextProps) {
    this.fetchUsers(nextProps.location.search);
  }

  fetchUsers = (search) => {
    const { page = 1 } = parse(search);
    UsersAPI.get(page).then(data => {
      this.setState({ users: data.users });
    });
  }

  render() {
    return (...);
  }
}
Collaborator

pshrmn commented Jun 13, 2017

@codeaid When the pathname stays the same, but the search string changes, you will still be matching with the same <Route>. This means that instead of the <Route> mounting a new component, it will simply update the existing one. That means that you will need to add update lifecycle methods to the component to get the new search string and fetch the data for it.

The below code should illustrate the idea:

// <Route path='/users' component={Users} />
class Users extends React.Component {
  componentWillMount() {
    this.fetchUsers(this.props.location.search);
  }

  componentWillUpdate(nextProps) {
    this.fetchUsers(nextProps.location.search);
  }

  fetchUsers = (search) => {
    const { page = 1 } = parse(search);
    UsersAPI.get(page).then(data => {
      this.setState({ users: data.users });
    });
  }

  render() {
    return (...);
  }
}
@codeaid

This comment has been minimized.

Show comment
Hide comment
@codeaid

codeaid Jun 14, 2017

@pshrmn Thanks for the tip! I somehow didn't realise that the router-aware components will get new properties pushed into them!

Ended up implementing a HOC which I can now use to inject searchQuery property (essentially location.search parsed into an object) into my components:

import * as React from 'react';
import {RouteComponentProps, withRouter} from 'react-router';
import {ISearchQueryComponentProps} from 'custom-types';
import {SearchQuery} from 'utils/SearchQuery';

export function withSearchQuery<P>(
    Component: React.SFC<P & ISearchQueryComponentProps> | React.ComponentClass<P & ISearchQueryComponentProps>
): React.ComponentClass<P & ISearchQueryComponentProps> {
    // higher order component class
    class WithSearchQuery extends React.Component<P & ISearchQueryComponentProps & RouteComponentProps<any>, {}> {
        /**
         * Render current component
         *
         * @return {JSX.Element}
         */
        render(): JSX.Element {
            // ensure router properties don't get passed down the hierarchy
            const {location, match, history, ...rest} = this.props as RouteComponentProps<any>;
            const query = new SearchQuery(location.search);

            return (
                <Component
                    {...rest}
                    searchQuery={query}
                />
            );
        }
    }

    return withRouter(WithSearchQuery);
}

Here's the SearchQuery class implementation, if anyone's interested:
https://gist.github.com/codeaid/eeca52172edcc194af875be8fc8ab3ed

To use it declare your component like this:

class MyCustomComponent extends React.Component<IMyCustomComponentProps, {}> {
    /**
     * Render current component
     *
     * @return {JSX.Element}
     */
    render() {
        const {searchQuery} = this.props;

        return (
            <pre>
                {JSON.stringify(searchQuery.getParams(), null, 2)}
            </pre>
        );
    }
}

export default withSearchQuery<IMyCustomComponentProps>(MyCustomComponent);

codeaid commented Jun 14, 2017

@pshrmn Thanks for the tip! I somehow didn't realise that the router-aware components will get new properties pushed into them!

Ended up implementing a HOC which I can now use to inject searchQuery property (essentially location.search parsed into an object) into my components:

import * as React from 'react';
import {RouteComponentProps, withRouter} from 'react-router';
import {ISearchQueryComponentProps} from 'custom-types';
import {SearchQuery} from 'utils/SearchQuery';

export function withSearchQuery<P>(
    Component: React.SFC<P & ISearchQueryComponentProps> | React.ComponentClass<P & ISearchQueryComponentProps>
): React.ComponentClass<P & ISearchQueryComponentProps> {
    // higher order component class
    class WithSearchQuery extends React.Component<P & ISearchQueryComponentProps & RouteComponentProps<any>, {}> {
        /**
         * Render current component
         *
         * @return {JSX.Element}
         */
        render(): JSX.Element {
            // ensure router properties don't get passed down the hierarchy
            const {location, match, history, ...rest} = this.props as RouteComponentProps<any>;
            const query = new SearchQuery(location.search);

            return (
                <Component
                    {...rest}
                    searchQuery={query}
                />
            );
        }
    }

    return withRouter(WithSearchQuery);
}

Here's the SearchQuery class implementation, if anyone's interested:
https://gist.github.com/codeaid/eeca52172edcc194af875be8fc8ab3ed

To use it declare your component like this:

class MyCustomComponent extends React.Component<IMyCustomComponentProps, {}> {
    /**
     * Render current component
     *
     * @return {JSX.Element}
     */
    render() {
        const {searchQuery} = this.props;

        return (
            <pre>
                {JSON.stringify(searchQuery.getParams(), null, 2)}
            </pre>
        );
    }
}

export default withSearchQuery<IMyCustomComponentProps>(MyCustomComponent);

@ReactTraining ReactTraining deleted a comment from dnsorlov Jun 15, 2017

@danikenan

This comment has been minimized.

Show comment
Hide comment
@danikenan

danikenan Jun 19, 2017

react-router 4 guys you are missing the point.

The support for qs in V3 was inadequate to begin with. You only provided parsing of the values, but no real support.

What you should have done in 4 is to make query string values a first class citizen in the router lib and allow routing based on them (just like params and url parts).

There is no reason to distinguish between values embedded in the path part and the query string.

All your answers regarding the ability to use 3rd party library for parsing the values, are only usable if you stay with the kind of support you offered in v3. For real query string support, they are not relevant.

Alos, you argue that you removed support due to multiple contradicting requirements for query serialization and deserialization. But this can be easily solved by exposing 2 functions properties where the user can specify his own strategy for serialization if she does not like yours.

Exposing the 2 function would have taken you less time than to read and answer all these angry people here, who have real needs from the field. And would have saved many hundreds of hours for the same people who see that all major routing library do what they need without over talking about it, and thus expect it.

danikenan commented Jun 19, 2017

react-router 4 guys you are missing the point.

The support for qs in V3 was inadequate to begin with. You only provided parsing of the values, but no real support.

What you should have done in 4 is to make query string values a first class citizen in the router lib and allow routing based on them (just like params and url parts).

There is no reason to distinguish between values embedded in the path part and the query string.

All your answers regarding the ability to use 3rd party library for parsing the values, are only usable if you stay with the kind of support you offered in v3. For real query string support, they are not relevant.

Alos, you argue that you removed support due to multiple contradicting requirements for query serialization and deserialization. But this can be easily solved by exposing 2 functions properties where the user can specify his own strategy for serialization if she does not like yours.

Exposing the 2 function would have taken you less time than to read and answer all these angry people here, who have real needs from the field. And would have saved many hundreds of hours for the same people who see that all major routing library do what they need without over talking about it, and thus expect it.

@asotog

This comment has been minimized.

Show comment
Hide comment
@asotog

asotog Jun 19, 2017

same issue here, don't see the query object containing query string parameters

asotog commented Jun 19, 2017

same issue here, don't see the query object containing query string parameters

@maxlapshin

This comment has been minimized.

Show comment
Hide comment
@maxlapshin

maxlapshin Jun 19, 2017

why do people speak here about only parsing query string: it only a part of the problem.

For example, we have a page where we filter objects by some query string. Clicking "search" button must change query string, replacing the old variable with a new one.

A resource on a web page is defined by path AND query string (and not anchor), so query string must be a part of routing mechanism. Putting query parameters to path usually leads to a very unmaintainable path and to a situation when there are many paths where filter segments take different places.

I have added query string parsing and handling to current version of react router, but previous versions were much more convenient and I had to write a lot of useless boilerplate code now.

I suppose that this question should be discussed more if you do not want to have pointless forks.

maxlapshin commented Jun 19, 2017

why do people speak here about only parsing query string: it only a part of the problem.

For example, we have a page where we filter objects by some query string. Clicking "search" button must change query string, replacing the old variable with a new one.

A resource on a web page is defined by path AND query string (and not anchor), so query string must be a part of routing mechanism. Putting query parameters to path usually leads to a very unmaintainable path and to a situation when there are many paths where filter segments take different places.

I have added query string parsing and handling to current version of react router, but previous versions were much more convenient and I had to write a lot of useless boilerplate code now.

I suppose that this question should be discussed more if you do not want to have pointless forks.

@menelike

This comment has been minimized.

Show comment
Hide comment
@menelike

menelike Jun 20, 2017

I don't have a strong opinion about rather query strings should be handled by react-router or not.

I only see two reasons why react router v4 dropped query strings parsing:

  1. Comparing strings is much easier than deep equality checks, so it's easier for PureComponents/shouldComponentUpdate to determine if something has changed

    • But this could also be achieved with a new parsed object when the queryString changes. So queryString === oldQueryString and queryObj === oldQueryObj can be both easily used to check for changes.
  2. Users requested control over querystring parsing

    • Imho this is absolutely valid #4410 (comment)
    • Also to be able to add a default parsing function on the top level Route would calm things down

menelike commented Jun 20, 2017

I don't have a strong opinion about rather query strings should be handled by react-router or not.

I only see two reasons why react router v4 dropped query strings parsing:

  1. Comparing strings is much easier than deep equality checks, so it's easier for PureComponents/shouldComponentUpdate to determine if something has changed

    • But this could also be achieved with a new parsed object when the queryString changes. So queryString === oldQueryString and queryObj === oldQueryObj can be both easily used to check for changes.
  2. Users requested control over querystring parsing

    • Imho this is absolutely valid #4410 (comment)
    • Also to be able to add a default parsing function on the top level Route would calm things down
@Serexx

This comment has been minimized.

Show comment
Hide comment
@Serexx

Serexx Jul 2, 2017

<RANT WARNING/>
<RANT>
With respect (I mean that ) to those contributing to React-Router, IMHO this reflects an increasingly frequent problem in the open source community : It has become almost 'normal' to simply drop, or arbitrarily re-work, features that leave users at best faced with unplanned/enforced maintenance to accommodate a lost feature or a 'breaking change' (a regrettable term I believe coined in early open source) or at worst, suddenly faced with a broken system (and angry stakeholders) when a version upgrade unexpectedly flips the proverbial goo into the proverbial fan, because (typically) there is no time for detailed unit testing with every open source package version upgrade.

I get that its a fast paced and liquid environment and that often its just a few contributors who keep a project on track, but imho 'good' change management practices are as important to long term viability (and popularity) of a project, as good coding practices.

As far as I can tell React-router is by no means a frequent or serious source of this kind of thing but this is certainly an example of it.

Although in this case, for me at least its not that big a deal (even if I happen to think dropping it is poor decision) - I just started using r-r so I'll go research another library but I pity poor Coder who has a serious investment in 3.X and is now cut off from the upgrade path without dropping 'n' other projects with waiting clients, to rework n^? others.

Multiply that by the number of packages that do this to Coder and it's no wonder its fast paced and liquid environment.

Just sayin.

</RANT>

Serexx commented Jul 2, 2017

<RANT WARNING/>
<RANT>
With respect (I mean that ) to those contributing to React-Router, IMHO this reflects an increasingly frequent problem in the open source community : It has become almost 'normal' to simply drop, or arbitrarily re-work, features that leave users at best faced with unplanned/enforced maintenance to accommodate a lost feature or a 'breaking change' (a regrettable term I believe coined in early open source) or at worst, suddenly faced with a broken system (and angry stakeholders) when a version upgrade unexpectedly flips the proverbial goo into the proverbial fan, because (typically) there is no time for detailed unit testing with every open source package version upgrade.

I get that its a fast paced and liquid environment and that often its just a few contributors who keep a project on track, but imho 'good' change management practices are as important to long term viability (and popularity) of a project, as good coding practices.

As far as I can tell React-router is by no means a frequent or serious source of this kind of thing but this is certainly an example of it.

Although in this case, for me at least its not that big a deal (even if I happen to think dropping it is poor decision) - I just started using r-r so I'll go research another library but I pity poor Coder who has a serious investment in 3.X and is now cut off from the upgrade path without dropping 'n' other projects with waiting clients, to rework n^? others.

Multiply that by the number of packages that do this to Coder and it's no wonder its fast paced and liquid environment.

Just sayin.

</RANT>

@asotog

This comment has been minimized.

Show comment
Hide comment
@asotog

asotog Jul 3, 2017

i was able to deal with it but had to made changes such as start using react-router-dom, history and query-string, these modules were not necessary with v3, also started to use this.props.history.listen((location) => { ... }) from my component to support what was doing with v3 .
As well when you define the routes have to wrap the routes with the container component and inside the switch and inside the routes like

render((
    <Provider store={store}>   
        <Router history={history}>
            <ProfilePreferences>
                <Switch>
                    <Redirect from={Cfg.basePath} exact to={ProfileRoutePaths.EDIT_PROFILE} />
                    <Route path={ProfileRoutePaths.EDIT_PROFILE} component={MyProfile}/>
                    <Route path={ProfileRoutePaths.EDIT_APPLICATIONS} component={MyApplications}/>
                    <Route path={ProfileRoutePaths.EDIT_SUBSCRIPTIONS} component={PagesFollowed}/>
                </Switch>
            </ProfilePreferences>
        </Router>
    </Provider>
), document.querySelector('user-profile'));

asotog commented Jul 3, 2017

i was able to deal with it but had to made changes such as start using react-router-dom, history and query-string, these modules were not necessary with v3, also started to use this.props.history.listen((location) => { ... }) from my component to support what was doing with v3 .
As well when you define the routes have to wrap the routes with the container component and inside the switch and inside the routes like

render((
    <Provider store={store}>   
        <Router history={history}>
            <ProfilePreferences>
                <Switch>
                    <Redirect from={Cfg.basePath} exact to={ProfileRoutePaths.EDIT_PROFILE} />
                    <Route path={ProfileRoutePaths.EDIT_PROFILE} component={MyProfile}/>
                    <Route path={ProfileRoutePaths.EDIT_APPLICATIONS} component={MyApplications}/>
                    <Route path={ProfileRoutePaths.EDIT_SUBSCRIPTIONS} component={PagesFollowed}/>
                </Switch>
            </ProfilePreferences>
        </Router>
    </Provider>
), document.querySelector('user-profile'));

@ReactTraining ReactTraining deleted a comment from Noiwex Jul 3, 2017

@FeloVilches

This comment has been minimized.

Show comment
Hide comment
@FeloVilches

FeloVilches Jul 6, 2017

@mjackson The only problem with your code is that you can't easily tell what's the meaning behind import stringifyQuery from 'whatever-lib-you-want'. This is pseudocode.

It's like explaining someone to do Something.method(stuff). It doesn't say anything, and it's difficult to tell what parts have to be replaced.

FeloVilches commented Jul 6, 2017

@mjackson The only problem with your code is that you can't easily tell what's the meaning behind import stringifyQuery from 'whatever-lib-you-want'. This is pseudocode.

It's like explaining someone to do Something.method(stuff). It doesn't say anything, and it's difficult to tell what parts have to be replaced.

@geraldchen890806

This comment has been minimized.

Show comment
Hide comment
@geraldchen890806

geraldchen890806 Jul 13, 2017

import queryString from 'query-string';

<Route path="xx" render={(props) => {
    let { history: { location = {} }, match = {} } = props;
    props = {
      ...props,
      history: {
        ...location,
        query: queryString.parse(location.search)
      },
      params: {
        ...match.params
      },
      routeParams: {
        ...match.params
      },
      location: {
        ...props.location,
        query: queryString.parse(location.search)
      }
    };
    return <Component {...props} />;
}} />

geraldchen890806 commented Jul 13, 2017

import queryString from 'query-string';

<Route path="xx" render={(props) => {
    let { history: { location = {} }, match = {} } = props;
    props = {
      ...props,
      history: {
        ...location,
        query: queryString.parse(location.search)
      },
      params: {
        ...match.params
      },
      routeParams: {
        ...match.params
      },
      location: {
        ...props.location,
        query: queryString.parse(location.search)
      }
    };
    return <Component {...props} />;
}} />
@goloveychuk

This comment has been minimized.

Show comment
Hide comment
@goloveychuk

goloveychuk Jul 17, 2017

that's how I dealed with it

import { createHashHistory, History as BaseHistory, LocationState, Path } from 'history';
import * as H from 'history'
import { HashRouterProps } from 'react-router-dom'
import { parse, stringify } from 'qs';





export class Location implements H.LocationDescriptorObject {
  private _location: H.Location
  private _cachedQuery: any = {}
  private _prevsearch?: string
  constructor(location: H.Location) {
    this._location = location
  }
  get query() {
    if (this._prevsearch !== this._location.search) {
      this._cachedQuery = parse(this._location.search, { ignoreQueryPrefix: true })
    }
    return this._cachedQuery
  }
  get pathname() {
    return this._location.pathname
  }
  get search() {
    return this._location.search
  }
  get state() {
    return this._location.state
  }
  get hash() {
    return this._location.hash
  }
  get key() {
    return this._location.key
  }

}

interface LocationDescriptorObject extends H.LocationDescriptorObject {
  query?: { [key: string]: any }
}


export class History implements BaseHistory {
  private _history: BaseHistory
  constructor(props: HashRouterProps) {
    this._history = createHashHistory(props)
  }
  get length() {
    return this._history.length
  }

  get action() {
    return this._history.action
  }

  get location() {
    return new Location(this._history.location)
  }

  push(location: LocationDescriptorObject): void
  push(path: H.Path, state?: H.LocationState): void
  push(a: H.Path | LocationDescriptorObject, b?: H.LocationState): void {
    if (typeof a === 'string') {
      return this._history.push(a, b)
    }
    if (a.query !== undefined) {
      a.search = stringify(a.query, { addQueryPrefix: true })
    }
    return this._history.push(a)
  }

  replace(location: LocationDescriptorObject): void
  replace(path: H.Path, state?: H.LocationState): void
  replace(a: H.Path | LocationDescriptorObject, b?: H.LocationState): void {
    if (typeof a === 'string') {
      return this._history.replace(a, b)
    }
    if (a.query !== undefined) {
      a.search = stringify(a.query, { addQueryPrefix: true })
    }
    return this._history.replace(a)
  }

  go(n: number) {
    return this._history.go(n)
  }

  goBack() {
    return this._history.goBack()
  }
  goForward() {
    return this._history.goForward()
  }
  block(prompt?: boolean) {
    return this._history.block(prompt)
  }

  listen(listener: H.LocationListener) {
    return this._history.listen(listener)
  }

  createHref(location: H.LocationDescriptorObject) {
    return this._history.createHref(location)
  }
}

and then

<Router history={new History()} >

goloveychuk commented Jul 17, 2017

that's how I dealed with it

import { createHashHistory, History as BaseHistory, LocationState, Path } from 'history';
import * as H from 'history'
import { HashRouterProps } from 'react-router-dom'
import { parse, stringify } from 'qs';





export class Location implements H.LocationDescriptorObject {
  private _location: H.Location
  private _cachedQuery: any = {}
  private _prevsearch?: string
  constructor(location: H.Location) {
    this._location = location
  }
  get query() {
    if (this._prevsearch !== this._location.search) {
      this._cachedQuery = parse(this._location.search, { ignoreQueryPrefix: true })
    }
    return this._cachedQuery
  }
  get pathname() {
    return this._location.pathname
  }
  get search() {
    return this._location.search
  }
  get state() {
    return this._location.state
  }
  get hash() {
    return this._location.hash
  }
  get key() {
    return this._location.key
  }

}

interface LocationDescriptorObject extends H.LocationDescriptorObject {
  query?: { [key: string]: any }
}


export class History implements BaseHistory {
  private _history: BaseHistory
  constructor(props: HashRouterProps) {
    this._history = createHashHistory(props)
  }
  get length() {
    return this._history.length
  }

  get action() {
    return this._history.action
  }

  get location() {
    return new Location(this._history.location)
  }

  push(location: LocationDescriptorObject): void
  push(path: H.Path, state?: H.LocationState): void
  push(a: H.Path | LocationDescriptorObject, b?: H.LocationState): void {
    if (typeof a === 'string') {
      return this._history.push(a, b)
    }
    if (a.query !== undefined) {
      a.search = stringify(a.query, { addQueryPrefix: true })
    }
    return this._history.push(a)
  }

  replace(location: LocationDescriptorObject): void
  replace(path: H.Path, state?: H.LocationState): void
  replace(a: H.Path | LocationDescriptorObject, b?: H.LocationState): void {
    if (typeof a === 'string') {
      return this._history.replace(a, b)
    }
    if (a.query !== undefined) {
      a.search = stringify(a.query, { addQueryPrefix: true })
    }
    return this._history.replace(a)
  }

  go(n: number) {
    return this._history.go(n)
  }

  goBack() {
    return this._history.goBack()
  }
  goForward() {
    return this._history.goForward()
  }
  block(prompt?: boolean) {
    return this._history.block(prompt)
  }

  listen(listener: H.LocationListener) {
    return this._history.listen(listener)
  }

  createHref(location: H.LocationDescriptorObject) {
    return this._history.createHref(location)
  }
}

and then

<Router history={new History()} >

@ReactTraining ReactTraining locked and limited conversation to collaborators Jul 19, 2017

@mjackson

This comment has been minimized.

Show comment
Hide comment
@mjackson

mjackson Jul 19, 2017

Member

There are many ways to get a query object in React Router v4. Any of the following should work fine:

// This is probably the most straightforward way. You'll get a `location`
// prop in your route component. Just parse `location.search` and away
// you go!
const HomePage = ({ location }) => {
  const query = new URLSearchParams(location.search)

  // When the URL is /the-path?some-key=a-value ...
  const value = query.get('some-key')
  console.log(value) // "a-value"
}

<Route ... component={HomePage}/>

If you are targeting a browser that does not support the URL API, a good option is the query-string package.

import qs from 'query-string'

const HomePage = ({ location }) => {
  const query = qs.parse(location.search)
}

Also, remember you get the route component's props in the render prop, so you could also just do this:

<Route ... render={({ location }) => {
  const query = qs.parse(location.search)
  // ...
}}/>

As mentioned earlier, more and more browsers are evolving to support URL parsing natively. In fact, only IE 11 doesn't natively support it as of this writing. So we opted to not include a query parsing library with React Router to keep things light and also give users more flexibility over URL parsing. Thanks for understanding!

Member

mjackson commented Jul 19, 2017

There are many ways to get a query object in React Router v4. Any of the following should work fine:

// This is probably the most straightforward way. You'll get a `location`
// prop in your route component. Just parse `location.search` and away
// you go!
const HomePage = ({ location }) => {
  const query = new URLSearchParams(location.search)

  // When the URL is /the-path?some-key=a-value ...
  const value = query.get('some-key')
  console.log(value) // "a-value"
}

<Route ... component={HomePage}/>

If you are targeting a browser that does not support the URL API, a good option is the query-string package.

import qs from 'query-string'

const HomePage = ({ location }) => {
  const query = qs.parse(location.search)
}

Also, remember you get the route component's props in the render prop, so you could also just do this:

<Route ... render={({ location }) => {
  const query = qs.parse(location.search)
  // ...
}}/>

As mentioned earlier, more and more browsers are evolving to support URL parsing natively. In fact, only IE 11 doesn't natively support it as of this writing. So we opted to not include a query parsing library with React Router to keep things light and also give users more flexibility over URL parsing. Thanks for understanding!

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.