Skip to content
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
Closed

No more parsing of query strings in V4? #4410

willemx opened this issue Jan 31, 2017 · 56 comments

Comments

@willemx
Copy link

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
Copy link

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
Copy link
Contributor

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
Copy link
Member

Solid answer, thank you @pshrmn :)

@jonrimmer
Copy link

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
Copy link
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>

@jochenberger
Copy link
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.

@mjackson
Copy link
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
Copy link
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.

@rubencodes
Copy link

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
Copy link
Member

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
Copy link
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
Copy link

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
Copy link

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
Copy link

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
Copy link

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
Copy link
Contributor

pshrmn commented Mar 17, 2017

@tkrotoff You can try qhistory.

@Lrigh1
Copy link

Lrigh1 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
Copy link

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
Copy link

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
Copy link

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
Copy link

lcoder commented Apr 24, 2017

@v1kku Thanks!

@Aiky30
Copy link

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!!!

@cryptiklemur
Copy link

cryptiklemur 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
Copy link

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
Copy link

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
Copy link
Member

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
Copy link

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
Copy link
Member

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
Copy link

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
Copy link
Member

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 remix-run/history#478

@odigity
Copy link

odigity commented May 19, 2017

Thanks @timdorr, will do.

@menelike
Copy link

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
Copy link

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
Copy link

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
Copy link

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
Copy link
Contributor

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
Copy link

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

@remix-run remix-run deleted a comment from dnsorlov Jun 15, 2017
@danikenan
Copy link

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
Copy link

asotog commented Jun 19, 2017

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

@maxlapshin
Copy link

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
Copy link

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

@Serexx
Copy link

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
Copy link

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

@remix-run remix-run deleted a comment Jul 3, 2017
@ChrisVilches
Copy link

ChrisVilches 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
Copy link

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
Copy link

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

@remix-run remix-run locked and limited conversation to collaborators Jul 19, 2017
@mjackson
Copy link
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!

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests