Router: Question/Feature request: Query parameters to props

Created on 7 Sep 2018  路  8Comments  路  Source: reach/router

Hi there,

New to reach-router, i'd like to match some query parameters to component props like this:

Browser route:
http://localhost/products/view?fullscreen&pageSize=10

Route Component to get:
<MyRouteComponent fullscreen pageSize="10"/>

Is it already somehow possible with reach-router and if yes then how?

Thank you in advance,
Lex

question

Most helpful comment

Hey there, @demigor ! I was trying to figure out the same as you, but as I'm also new here, please take what I say with a grain of salt.

The location prop passed down to RouteComponents is an object that contains a search field. This search field contains all the query parameters (query strings) from your URL. In your example, /products/view?fullscreen&pageSize=10 would return location.search: ?fullscreen&pageSize=10, which is a query string.

In order to work with this query, you can use the simple query-string library as the following example:

import * as queryString from 'query-string';

...
// inside MyRouteComponent
export const MyRouteComponent = props => {
  const queryParams = queryString.parse(props.location.search);
  /* queryParams: {
      fullscreen: null,
      pageSize: 10,
    }
  */
  ...
}

Go ahead and do a few experiments and you'll easily get the hang of it! Any questions lemme know :smile:

PS: @ryanflorence I think the documentation is lacking in this regard as I believe lots of newbies like me don't necessarily understand the location prop... lemme know what you think, I'd be glad to help!

All 8 comments

Hey there, @demigor ! I was trying to figure out the same as you, but as I'm also new here, please take what I say with a grain of salt.

The location prop passed down to RouteComponents is an object that contains a search field. This search field contains all the query parameters (query strings) from your URL. In your example, /products/view?fullscreen&pageSize=10 would return location.search: ?fullscreen&pageSize=10, which is a query string.

In order to work with this query, you can use the simple query-string library as the following example:

import * as queryString from 'query-string';

...
// inside MyRouteComponent
export const MyRouteComponent = props => {
  const queryParams = queryString.parse(props.location.search);
  /* queryParams: {
      fullscreen: null,
      pageSize: 10,
    }
  */
  ...
}

Go ahead and do a few experiments and you'll easily get the hang of it! Any questions lemme know :smile:

PS: @ryanflorence I think the documentation is lacking in this regard as I believe lots of newbies like me don't necessarily understand the location prop... lemme know what you think, I'd be glad to help!

You can also supply a custom history created by qhistory package to include location.query field with parsed params object:

import { createHistory, LocationProvider } from '@reach/router'
import qhistory from 'qhistory'
import { stringify, parse } from 'qs'

const history = qhistory(
  createHistory(window),
  stringify,
  parse
)

export const App = () => (
  <LocationProvider history={history}>
    {<your app here>}
  </LocationProvider>
)

@Hypnosphi I am having issues with navigation if Router component is inside the location Provider. How did you set it up?

qhistory and reach history have different APIs so my actual code looks like that:

import createBrowserHistory from 'history/createBrowserHistory'
import qhistory from 'qhistory'
import { stringify, parse } from 'qs'

const history = qhistory(createBrowserHistory(), stringify, parse)

const historySource = {
  get state() {
    return history.location.state
  },
  pushState: (state, _, to) => history.push(to, state),
  replaceState: (state, _, to) => history.replace(to, state),
}

const UnlistenMap = new WeakMap()

const reachHistory = createHistory({
  addEventListener(event, listener) {
    if (event === 'popstate') {
      UnlistenMap.set(listener, history.listen(listener))
    } else {
      window.addEventListener(...arguments)
    }
  },
  removeEventListener(event, listener) {
    if (event === 'popstate') {
      const unlisten = UnlistenMap.get(listener)
      if (unlisten) {
        unlisten()
      }
    } else {
      window.removeEventListener(...arguments)
    }
  },
  get location() {
    return {
      ...window.location,
      ...history.location,
    }
  },
  set location(value) {},
  history: historySource,
})

export const App = () => (
  <LocationProvider history={reachHistory}>
    <Router>
       {<my routes>}
    </Router>
  </LocationProvider>
)

For a SSR setting, you might need two things:

  • A page context to wrap page props into PageContext
  • A useQueryParams hook for deep children to get props from page component

pageContext.js:

import React, { createContext } from 'react'

const pageContext = createContext()

export const withPageContext = Component => props => {
  return (
    <pageContext.Provider value={{...props}}>
      <Component {...props} />
    </pageContext.Provider>
  )
}

export default pageContext

useQueryParams.js

import pageContext from 'utils/pageContext'
import {useContext} from 'react'
import { parse, parseUrl } from 'query-string'
const isServer = typeof(window) === 'undefined'

const useQueryParams = () => {
  const {location} = useContext(pageContext)
  const queryParams = isServer ?  
    parseUrl(location.pathname).query : 
    parse(location.search);

  return queryParams
};

export default useQueryParams;

You need to wrap route page component with withPageContext then you're good to use useQueryParams on children.
Hope this helps.

@revskill10 I added this, but clicking on back button doesn't render the Components again. Any solution for that?

Going off of the OP and seeing that they were correctly directed to destructure the location object.

There's also a few previous issues that suggest documenting how what the location object would provide some good value, so I'll go ahead and close out this issue.

Thanks for helping to make reach router a better router!

Hook based on useLocation:

import { useLocation } from "@reach/router";
import qs from "qs";

export default () => {
  const location = useLocation();
  const queryParams = qs.parse(location.search, {
    ignoreQueryPrefix: true,
  });
  return queryParams;
};
Was this page helpful?
0 / 5 - 0 ratings

Related issues

EJIqpEP picture EJIqpEP  路  4Comments

kbrgl picture kbrgl  路  4Comments

ryanflorence picture ryanflorence  路  4Comments

jsonmaur picture jsonmaur  路  4Comments

michaelwiktorek picture michaelwiktorek  路  4Comments