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)
)
Related
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
I am using react-router's built in function generatePath to generate a URL. The issue is that as far as I understand this function just returns the path and doesn't provide a mechanism for us to know which fields were added in the path and which were not.
For example, for the following code
generatePath('/user/:id', {
id: 1,
name: 'John',
})
the function returns /user/1 which is correct, but there is no way for us to know that only id was inserted into the path and name needs to be passed as query parameters.
In my application both the path template and the params object are dynamic and I need to add the extra fields in params as the query parameters.
Is there any way to do that ?
For anyone checking now, I ended up using the path-to-regexp library which is the one used internally by react-router to generate the URL. The code looks something like this
import pathToRegexp from 'path-to-regexp';
import qs from 'qs';
const compiledCache = {};
export default function generatePathWithQueryParams(rawPath, params) {
let toPath;
if (compiledCache[rawPath]) {
toPath = compiledCache[rawPath];
} else {
toPath = pathToRegexp.compile(rawPath);
compiledCache[rawPath] = toPath;
}
const queryParams = { ...params };
const path = toPath(params, {
encode: (value, token) => {
delete queryParams[token.name];
return encodeURI(value);
},
});
const queryString = qs.stringify(queryParams);
if (queryString) {
return `${path}?${queryString}`;
}
return path;
};
I am trying to update my chache after succesfully executing a mutation. Here is my query and mutation:
export const Dojo_QUERY = gql`
query Dojo($id: Int!){
dojo(id: $id){
id,
name,
logoUrl,
location {
id,
city,
country
},
members{
id
},
disziplines{
id,
name
}
}
}`;
export const addDiszipline_MUTATION = gql`
mutation createDisziplin($input:DisziplineInput!,$dojoId:Int!){
createDisziplin(input:$input,dojoId:$dojoId){
disziplin{
name,
id
}
}
}`;
and my mutation call:
const [createDisziplin] = useMutation(Constants.addDiszipline_MUTATION,
{
update(cache, { data: { createDisziplin } }) {
console.log(cache)
const { disziplines } = cache.readQuery({ query: Constants.Dojo_QUERY,variables: {id}});
console.log(disziplines)
cache.writeQuery({
...some update logic (craches in line above)
});
}
}
);
when i execute this mutation i get the error
Invariant Violation: "Can't find field dojo({"id":1}) on object {
"dojo({\"id\":\"1\"})": {
"type": "id",
"generated": false,
"id": "DojoType:1",
"typename": "DojoType"
}
}."
In my client cache i can see
data{data{DojoType {...WITH ALL DATA INSIDE APPART FROM THE NEW DISZIPLINE}}
and
data{data{DisziplineType {THE NEW OBJECT}}
There seems to be a lot of confusion around the client cache around the web. Somehow none of the posed solutions helped, or made any sense to me. Any help would be greatly appreciated.
EDIT 1:
Maybe this can help?
ROOT_QUERY: {…}
"dojo({\"id\":\"1\"})": {…}
generated: false
id: "DojoType:1"
type: "id"
typename: "DojoType"
<prototype>: Object { … }
<prototype>: Object { … }
Edit 2
I have taken Herku advice and started using fragment. however it still seems to not quite work.
My udated code:
const [createDisziplin] = useMutation(Constants.addDiszipline_MUTATION,
{
update(cache, { data: { createDisziplin } }) {
console.log(cache)
const { dojo } = cache.readFragment(
{ fragment: Constants.Diszilines_FRAGMENT,
id:"DojoType:"+id.toString()});
console.log(dojo)
}
}
);
with
export const Diszilines_FRAGMENT=gql`
fragment currentDojo on Dojo{
id,
name,
disziplines{
id,
name
}
}
`;
however the result from console.log(dojo) is still undefined.Any advice?
So I think your actual error is that you have to supply the ID as as a string: variables: {id: id.toString()}. You can see that these two lines are different:
dojo({\"id\":1})
dojo({\"id\":\"1\"})
But I would highly suggest to use readFragment instead of readQuery and update the dojo with the ID supplied. This should update the query as well and all other occurrences of the dojo in all your queries. You can find documentation on readFragment here.
Another trick is as well to simply return the whole dojo in the response of the mutation. I would say people should be less afraid of that and not do to much cache updates because cache updates are implicit behaviour of your API that is nowhere in your type system. That the new disziplin can be found in the disziplins field is now encoded in your frontend. Imagine you want to add another step here where new disziplins have to be approved first before they end up in there. If the mutation returns the whole dojo a simple backend change would do the job and your clients don't have to be aware of that behaviour.
Was working in react and then encountered a problem like
Uncaught URIError: This is likely caused by an invalid percent-encoding
Im working with news API at the moment and some of the articles might include %. My entire app depends on displaying news articles names in url because im using this.props.match.params.id
I tried to search for a solution online but most of them are very unclear when it comes to solving this exact problem.
Is there a simple workaround this issue?
You need to use encodeURIComponent() with the path you receive as parameter:
Example:
const receivedArticleName = encodeURIComponent('Article Name with %');
Since you are working with an API, once you receive it, set your URL variable with that receivedArticleName and you're done.
This did the trick for me.
function navigate(data: Partial<Saturation>) {
if (data.name) {
const checkSyrupForPercentChar = data.name.includes('%')
const syrupReplacementName = data.name.replace('%', '')
history.push({
pathname: `saturation-directory/${data.id}/${urlFormat(
checkSyrupForPercentChar ? syrupReplacementName : data.name
)}`,
state: {
syrupData: data,
from: 'syrupDirectory'
}
})
}
}
The code before refactor:
function navigate(data: Partial<Saturation>) {
if (data.name) {
history.push({
pathname: `saturation-directory/${data.id}/${urlFormat(data.name)}`,
state: {
syrupData: data,
from: 'syrupDirectory'
}
})
}
}
The takeaway is the string functions that I incorporated as well are the ternary operator on the pathname.
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.