React Router: Handling queries through React router - reactjs

So, currently, I have a routing component:
<Route path="/lists/:query" component={Lists} />
I get a call like:
http://localhost:4567/lists/page=17&city_codes=2567
In my Lists component, I handle this query in this way:
componentDidMount() {
const query = match.params.query;
const cleanQueryString = query.replace(/[|;$%#"<>()+,]/g, '');
// break up the string using '&' and '=' into an object
const properties = this.queryURL(cleanQueryString);
const cleanQueryObj = _.pick(Object.assign({}, ...properties), [
'page',
'city_codes',
'min_monthly_fee',
'max_monthly_fee',
'order_by',
]);
// update the query object based on component state
this.setState({ query: cleanQueryObj }, () => {
cleanQueryObj.page && this.updateIndex(parseInt(cleanQueryObj.page, 10));
});
// call axios request and update redux
dispatch(handleLists(cleanQueryObj));
// update browser url
this.props.history.push(cleanQueryObj);
Now, I see a lot of major sites using ?q= before the query and I'm wondering what I'm missing or what could be improved?
Thoughts?

While what you are doing is technically valid, it is a bit non-standard. The way you use the router :query param and the way it is formatted, reaaaaly looks like an actual location.search parameter format, and not a path parameter.
A more standard way to do it, would be with the following URL:
http://localhost:4567/lists?page=17&city_codes=2567
And code as follow:
// In your routes, just a simple route with no path params
<Route path="/lists" component={Lists} />
// In your component
import queryString from 'query-string'
[...]
componentDidMount() {
// Use location object from react-router
const { search } = this.props.location
// Parse it using a npm dedicated module
const { page, city_codes } = queryString.parse(search)
// Now you can use those params
]);
Edit: and now an actual answer to the question:
?q=blah is usually used in a search context, with q parameter being a string used to search something. There can be other parameters following for example ?q=blah&ext=txt.
It is hence different from your :query path param, which is encoded to contain multiple parameters, while q here is a single ready-to-use parameter.

Related

How to pass multiple queries in next router

I am working in next.js and next-router
I have 2 data parameters that I want to pass
One is entity_id and the other is url_key.
data={
entity_id: 5,
url_key: 'canada/ontario/store_five'
}
Currently I am able to pass one url_key:
Router.push('/store?url_key=' + marker.url_key, `/store/${marker.url_key}`)
The URL is appearing just as I wanted like
http://BaseUrl/store/canada/ontario/store_five
Now I want to also send entity_id along with above url_key but that should not display in URl
You can pass as many query params as you want, it just using query-string.
// using urls
Router.push(
`/store?url_key=${marker.url_key}&entity_id=${marker.entity_id}`,
`/store/${marker.url_key}`
);
// using object
Router.push({
pathname: '/store',
query: { url_key: marker.url_key, entity_id: marker.entity_id },
asPath: `/store/${marker.url_key}`,
});
For more info, read router docs
I would suggest you use a query object to pass multiple queries in next router. Using package
import {useRouter} from "next/router";
const router=useRouter();
router.push({
pathname:'/store',
query:{entity_id :"2221ACBD",url_key:"URL KEY"},
})
To fetch the data from the query you can use array destructuring of query like this :
const { query } = useRouter();
console.log("query::",query);
console.log("entity key:-",query.entity_id);
console.log("url_key:-",query.url_key);
Example : Example1

Get multiple URL parameters using useParams() hook

I am trying to pass multiple parameters in a url using React-Router 5.1 and the useParams hook in a Functional component.
However I encounter really strange behavior.
I paste the url into the client.
Url:
http://localhost:3000/onboarding/eventId=5e9a173f9166f800128c04d1&groupId=5e9a173f9166f800128c04dc&userId=5e9a173f9166f800128c04da
Path:
<Route path = '/onboarding/:eventId?/:groupId?/:userId?' exact component = {OnboardingViewController} />
Strange thing #1:
I have to make the params optional, or the browser just hangs forever.
I fish them out using all these strategies:
var { eventId, groupId, userId } = useParams();
var { eventId } = useParams();
var { groupId } = useParams();
var { userId } = useParams();
Strange thing #2:
Unfortunately when trying to use these params this happens:
{userId: undefined, eventId: "eventId=5e9a173f9166f800128c04d1&groupId=5e9a173f9166f800128c04dc&userId=5e9a173f9166f800128c04da", groupId: undefined}
The hook just takes the first param and interprets the rest a part of te first.
Strange thing #3:
Since adding this url params query accessing the page laoding is extremely slow, multiple seconds.
What am I not seeing, doing wrong?
ANSWER:
What I was doing wrong:
I was using url/eventId=123.
This is wrong.
You just need to supply the resource at the right place in the URL url/1/2/3.
correct:
http://localhost:3000/onboarding/5e9aaf4fc27583001190834e/5e9aaf60c275830011908361/5e9aaf4fc275830011908357
You then tell the Router that those things will be called eventId & groupId & userId.
<Route path = '/onboarding/:eventId/:groupId/:userId' exact component = {OnboardingViewController} />
Then you can access these in the component using the userPrams() hook.
var { eventId, groupId, userId } = useParams();
Thanks everyone!
Your Route structure and Route doesn't match
If you want to use params in your URL would be
http://localhost:3000/onboarding/5e9a173f9166f800128c04d1/5e9a173f9166f800128c04dc/5e9a173f9166f800128c04da
And your Route component would be:
<Route path = '/onboarding/:eventId/:groupId/:userId' exact component = {OnboardingViewController} />
And then you can use this in the OnboardingViewControllercomponent:
var { eventId, groupId, userId } = useParams();
console.log(eventId,groupId,userId)
You are mixing up match parameters with URL query parameters.
The URL query parameters can be retrieved from the location object using the useLocation hook.
Given URL http://localhost:3000/onboarding/?eventId=5e9a173f9166f800128c04d1&groupId=5e9a173f9166f800128c04dc&userId=5e9a173f9166f800128c04da
{
pathname: '/onboarding/',
search: '?eventId=5e9a173f9166f800128c04d1&groupId=5e9a173f9166f800128c04dc&userId=5e9a173f9166f800128c04da'
}
would need a route path="/onboarding/" though
You can use a QueryParameter processing library to then convert these to a map object.
If you could massage your URL to be in the form:
http://localhost:3000/onboarding/5e9a173f9166f800128c04d1/5e9a173f9166f800128c04dc/5e9a173f9166f800128c04da
Then the route path='/onboarding/:eventId/:groupId/:userId' can then match the path params returned by useParams.
const { eventId, groupId, userId } = useParams();

How do I add a query param to Router.push in NextJS?

With NextJS, I'm trying to route to another page using an object that has a to and as field:
export const routes = {
'BrowseList' : {
'to' : '/apps/Browse/list',
'as' : '/browse/list'
}
// ....
}
and then that's imported and used like so:
import { routes } from './__routes';
import Router from 'next/router';
// ....
const { to, as } = routes.BrowseList;
Router.push(to, as);
which all works. My dilemma is that I'm trying to do something similar to this while attaching a query param. I'm trying to follow this example according to the docs:
Router.push({
pathname: '/about',
query: { name: 'Zeit' },
})
What I've tried (which doesn't work):
Router.push({
pathname : to,
as,
query : { user_id: this.props.data.member.user.id },
});
which gives me a console warning of
Unknown key passed via urlObject into url.format: as
I know I can maybe possibly just use string interpolation and do something like this:
Router.push(to, `${as}?user_id=`${this.props.data.member.user.id}`)
but I was wondering if there was something I'm missing in the doc's example that also adds the query param into my as value.
Thank you.
You were close #nyphur. The as value goes as the second parameter of push and not inside the object that corresponds to to (check router.d.ts to see how push is defined). That's why you're getting the error Unknown key passed via urlObject into url.format: as. After 10 months from your question maybe this could still be useful to someone looking for an answer. Assuming you have a way to build the query string for the as parameter, following #Gezim answer or by any other approach:
Router.push({ pathname: to, query: { user_id: this.props.data.member.user.id } }, as, options);
NOTE: Based on #Gezim answer, if you format the string or pathname in the first parameter to contain your query params, it'll work BUT encoded values, if any, like %2B for instance will be decoded so you will get +. This doesn't happen if the query params object go inside query. Consider this if you have any kind of logic that depends on this.
It appears that the router in next.js doesn't have any convenient API to navigate to using a query string.
I created a utility class called LinkCreator with a toQuery method as follows. It uses query-string to create the query string:
import * as qs from 'query-string';
export class LinkCreator {
static query(object) {
return qs.stringify(object);
}
static toQuery(object, path = "/") {
const query = this.query(object);
return path + '?' + query;
}
}
Then, it can be used with Router.push like so:
Router.push(LinkCreator.toQuery({ name: 'Zeit' }), '/about');
Edit: at first I thought merging an object via spreading would be an easy fix, but then as a comment pointed out there needed to be some changes, so I have updated my answer to still utilize spreading, but unfortunately it does now make the Routes more complicated and involved, but the consumption of it is still straight forward.
I would also freeze the Routes object for peace of mind as well.
import Router from 'next/router';
export const Routes = Object.freeze({
BrowseList(query) {
return [
{
pathname: '/apps/Browse/list',
query
},
'/browse/list'
]
}
// ....
})
Router.push(
...Routes.BrowseList({
paramName: "Param value here"
})
)
Additional Abstraction
import Router from 'next/router';
const QueryRoutePath = (to, as, query) => ([
{
pathname: to,
query
},
as
])
export const Routes = Object.freeze({
BrowseList(query) {
return QueryRoutePath(
'/apps/Browse/list',
'/browse/list',
query)
}
// ....
})
const query = {
paramName: "Param value here"
}
Router.push(
...Routes.BrowseList(query)
)

How to create dynamic route in gatsby

I have setup gatsby project using this link. It is working correctly.
Now I know how to create route by defining the component inside the pages folder. But now I have a new challenge I need to create one dynamic route so that I can pass my id in it (Just like reactjs).
<Route path: "/path/:id"/>
How do I do that in gatsby?
You have to explicitly tell gatsby that a path should be dynamic. From the docs:
// gatsby-node.js
// Implement the Gatsby API “onCreatePage”. This is
// called after every page is created.
exports.onCreatePage = async ({ page, actions }) => {
const { createPage } = actions
// page.matchPath is a special key that's used for matching pages
// only on the client.
if (page.path.match(/^\/app/)) {
page.matchPath = "/app/*"
// Update the page.
createPage(page)
}
}
and then you can use dynamic routing in src/pages/app.js
import { Router } from "#reach/router"
const SomeSubPage = props => {
return <div>Hi from SubPage with id: {props.id}</div>
}
const App = () => (
<Layout>
<Link to="/app/1">First item</Link>{" "}
<Link to="/app/2">Second item</Link>{" "}
<Router>
// ...dynamic routes here
<SomeSubPage path="/app/:id" />
</Router>
</Layout>
)
export default App
Everything that goes to /app/* will be handled dynamically now. You should find your id as usual in the props.
Have a look at their authentication example https://github.com/gatsbyjs/gatsby/tree/master/examples/simple-auth
You can use square brackets ([ ]) in the file path to mark any dynamic segments of the URL. For example, in order to edit a user, you might want a route like /user/:id to fetch the data for whatever id is passed into the URL.
src/pages/users/[id].js will generate a route like /users/:id
src/pages/users/[id]/group/[groupId].js will generate a route like /users/:id/group/:groupId
Reference: https://www.gatsbyjs.com/docs/reference/routing/file-system-route-api#creating-client-only-routes
You can use gatsby-plugin-create-client-paths. It uses matchPath. For more info check
https://www.gatsbyjs.org/docs/gatsby-internals-terminology/#matchpath
https://www.gatsbyjs.org/packages/gatsby-plugin-create-client-paths/
This answer is Super late, but for anyone in the future who is faced with this problem, I have a simpler solution.
In Gatsby terms it's called a Splat Route.
For examples, If you want some page "domain.com/profile/[id]", where id can be any number, which will be used to display different data inside the website, you should name your page as [...id].
Now inside the page you can access this id as
const ProfilePage = (props) => <div>This page is for id number {props.params.id}</div>
Note: Don't miss the 3 dots, that is what signifies a splat route in gatsby.

Programmatically navigate using react router without duplicating paths

I've read this post, but I don't like having browserHistory.push('/some/path') in one of my components while I have <Route path="/some/path" component={SomePage} /> in my router file since the path is duplicated. What if I want to change that path to /another/path? Now I need to remember to update it in the router file and also my component.
Is there a better way around this? I was thinking that I could have "/some/path" and all my other paths defined in some constants file that gets imported and referenced in my router and my component. Example:
paths.js
var Paths = {
myPath: "/some/path",
...
}
module.exports = Paths
router.jsx
var Paths = require('constants/paths');
...
<Route path={Paths.myPath} component={SomePage} />
component.jsx
var Paths = require('constants/paths');
...
browserhistory.push(Paths.myPath)
This seems like it could get a little messy when dealing with URL parameters like /some/path/:id, so I was hoping there might be a better way.
This is what I have done in the past for routing to make it simpler / more streamlined.
(as a side note i used lodash here so if you aren't you can use native functions to do basically the same thing. lodash just adds a bunch of nice features / functions that you dont need to go write yourself)
in my routes.jsx file you should create functions that convert any parameters into a url with a default path for this answer lets just make one for a profile route
export function pathToProfile(userName, params) {
return path(Paths.PROFILE, _.assign({userName}, params));
}
the path() function is just a simple helper utility function for generating a path.
path(url, params, urlMap) {
if(!url) {
console.error("URL is not defined for action: ", params);
}
if(!params)
return url;
params = _.merge({}, params);
if(urlMap) {
_.keys(urlMap).forEach((k) => {
params[urlMap[k]] = params[k];
delete params[k];
});
}
if(url.indexOf(":") !== -1) {
url.match(/:([0-9_a-z]+)/gi).forEach((match)=>{
var key = match.replace(":", "");
url = url.replace(match, params[key]);
params = _.omit(params, key);
});
}
if(_.keys(params).length > 0) {
url = url + "?" + this.paramsToString(params);
}
return url;
}
now looking at the constants file
Paths {
PROFILE: '/user/:username',
}
Finally usage.
I wouldn't recommend broswerHistory.push() when you can have an onClick handler. Yes it works and will redirect, but is it the best thing to use? react-router also has a Link that should be used wherever possible. you get some nice additional features one for example would be an active route for whatever page you're on.
browserHistory.push() is a good way to handle redirecting when you do things like an auth login redirect or if you are responding to data from a request and conditionally taking them to a page.
<Route path={Paths.PROFILE} component={Profile} />
<Link to={pathToProfile(this.props.user.username, {myParams: 'here'})></Link>
what that would be translated into if you wanted to see the url from that exact link it would be /user/someUsername?myParams=here

Resources