My URL currently looks as following.
http://localhost:3000/path/label?default=1111
I want to append additional query string values. Thus after appending, I am expecting it to appear as follows.
http://localhost:3000/path/label?name=name&age=100&default=ab07
Current logic works when I look at it in the browser URL.
I see this:
http://localhost:3000/path/label?name=name&age=100&default=ab07
But when I console print it:
console.log(`router: ${JSON.stringify(router.location.search)}`)
I end up printing following:
router: "?default=1111"
It should have printed:
router: "?name=name&age=100&default=ab07"
Thought maybe the router updates fine, just some delay in print.
But that is not the case.
Once I have amended the query params. I need to push it to another url with same params.
At this stage it is still wrongly only holding the initial query params and ignoring my new params. Why?
Expected to push to:
newPath/?name=name&age=100&default=ab07
Incorrectly pushing to:
newPath?default=ab07
This is how I am appending the query and search params.
export const add = (router, data) => {
const pathname = router.location.pathname;
let updatedQuery = {
...router.location.query,
};
if (hasValue) {
const details = {
name: 'name',
'age': '100'
};
updatedQuery = {
...updatedQuery,
...details,
};
}
router.replace({
pathname,
query: updatedQuery,
search: new URLSearchParams(updatedQuery),
});
}
This is how I am pushing it to another path while still wanting to retain the same query params.
// This function is in another file.
redirectToAnotherPage = (router, data) => {
// other logic
return `/newPath/${router.location.search}`;
}
This is how I am calling both above functions.
import { withRouter } from 'react-router';
const constructAndRedirect() {
// other logic
add(router, data)
router.push(redirectToAnotherPage(router, data));
}
Let's say I have a very basic API with two sets of endpoints. One set queries and mutates properties about a User, which requires a username parameter, and one set queries and mutates properties about a Post, which requires a post ID. (Let's ignore authentication for simplicity.) I don't currently see a good way to implement this in a DRY way.
What makes the most sense to me is to have a separate Context for each set of routes, like this:
// post.ts
export async function createContext(
opts?: trpcExpress.CreateExpressContextOptions
) {
// pass through post id, throw if not present
}
type Context = trpc.inferAsyncReturnType<typeof createContext>;
const router = trpc
.router()
.query("get", {
resolve(req) {
// get post from database
return post;
},
});
// similar thing in user.ts
// server.ts
const trpcRouter = trpc
.router()
.merge("post.", postRouter)
.merge("user.", userRouter);
app.use(
"/trpc",
trpcExpress.createExpressMiddleware({
router: trpcRouter,
createContext,
})
);
This complains about context, and I can't find anything in the tRPC docs about passing a separate context to each router when merging. Middleware doesn't seem to solve the problem either - while I can fetch the post/user in a middleware and pass it on, I don't see any way to require a certain type of input in a middleware. I would have to throw { input: z.string() } or { input: z.number() } on every query/mutation, which of course isn't ideal.
The docs and examples seem pretty lacking for this (presumably common) use case, so what's the best way forward here?
This functionality has been added in (unreleased as of writing) v10. https://trpc.io/docs/v10/procedures#multiple-input-parsers
const roomProcedure = t.procedure.input(
z.object({
roomId: z.string(),
}),
);
const appRouter = t.router({
sendMessage: roomProcedure
.input(
z.object({
text: z.string(),
}),
)
.mutation(({ input }) => {
// input: { roomId: string; text: string }
}),
});
Suppose I have this url:
/app/search?spaceId=**621cd16015**&resource_type=room&capacity=3
and I want to update only the spaceId based on click, so the new URL will be:
/app/search?spaceId=**14235ad**&resource_type=room&capacity=3
I'm using the class component. I used the following code to change the spaceId. It works, but it also removes all the other parameters too.
this.props.history.push({
pathname: "/app/search",
search: `?spaceId=${event}`,
});
You can use URLSearchParams object
const searchParams = new URLSearchParams('spaceId=spaceOldValue&resource_type=room&capacity=3');
searchParams.set('spaceId', 'spaceNewValue');
this.props.history.replace({
pathname: '/app/search',
search: searchParams.toString()
});
I'm starting with Next.js and after going through docs, I cannot figure out how to get the route param code inside getStaticPaths method as shown below!?. code is not known before hand by any means and it can be anything.
I don't want to call api and get the data using useEffect inside the component.
File: pages/post/[code].js
import React from 'react';
import apiCall from 'api/something';
export default ({post}) => {
return <>
render components here based on prop `post`
</>
}
export async function getStaticPaths() {
// How to get [code] from the route here, which can be used below?
return {
paths: // NEED [code] HERE from current route,
fallback: false
}
}
export async function getStaticProps(ctx) {
return {
props: {
// [ctx.code] resolved from current route with the help of getStaticPaths,
post: apiCall(ctx.code)
}
}
}
I've tried getServerSideProps which works for me:
export const getServerSideProps = async (ctx) => {
return {
props: {
post: await apiCall(ctx.query.code)
}
};
};
But it fails when I do next export stating:
pages with getServerSideProps can not be exported. See more info here: https://err.sh/next.js/gssp-export
After investigating further on this error I found this solution, which is not feasible for me as my app is hosted on Heroku.
I'm trying to server-side render the html along with the data based on the route param code. But not able to do so now.
The purpose of the function getStaticPaths is to generate a list of paths for which static HTML will be rendered at build time. For example, for a list of 10 posts, you can generate 10 posts/[id] routes ahead of time if you know the id of the posts.
How getStaticPaths works with dynamic routes in more details..
Suppose you have a dynamic route /posts/[postId] if you choose to use static-generation you have to generate a list of paths that will include the postId as a route param and for each path returned, the function getStaticProps will be called to query the data at build time. Example,
// for /post/[postId]
export const getStaticPaths = async () => {
// if you know all the postId ahead of time
const paths = [
{ params: { postId: '1234' } }, // keep in mind postId has to be a string
{ params: { postId: '3792' } },
{ params: { postId: '1749' } },
]
return {
paths,
fallback: false // we are disabling fallback because we know all the paths ahead of time
}
}
// for each path returned getStaticProps will be called at build time
export const getStaticProps = async (context) => {
// you have access to the postId params that you returns from
// getStaticPaths here
const postId = context.params.postId
// now you can query the data from postId and return as props
return {
props: // queried data
}
}
If fallback is set to false any for any route path that is not returned from the function getStaticPaths nextjs will simply show a 404 error page.
How to use fallback: true to generate static pages for route params not known ahead of time
If you know some postId of the posts and the data for the posts do not change very often, you can choose to generate the pages with fallback property set to true, which will display a fallback version of the page for the paths that are not returned from the function getStaticPaths. And on request for the page nextjs will call getStaticProps and send the data as JSON which will be used to render the page in the browser.
Example,
// for /post/[postId]
export const getStaticPaths = async () => {
// you can get how many ever postIds are know ahead of time
// and return as paths with fallback set to true
const posts = // queried data from db or fetched from remote API
const paths = posts.map(post => { params:{ postId: post.id.toString() }})
return {
paths,
fallback: true
}
}
// in your page Component check for fallback and render a loading indicator
import { useRouter } from 'next/router';
const MyPage = (props) => {
// before you do anything
const router = useRouter();
if (router.isFallback) {
return <div>Loading....</div>
}
// rest of your page logic
}
If your data is very dynamic, let's say changing every 30mins or an hour or so. You can choose to use server-side rendering which will fetch the data on per request basis, but TTFB(time to first byte) will be higher. For example,
// for /post/[postId]
export const getServerSideProps = async (context) => {
// you also have access to the param postId from the context
const postId = context.params.postId
// query the data based on the postId and return as props
return {
props: // queried data
}
}
Keep in mind if you choose to go with getServerSideProps the function will be called on per-request basis so time to first byte will be higher.
Depending on use-cases you can also use static generation with client-side data fetching using swr from nextjs team repo link.
As I understand, you want to statically generate dynamic routes at build time.
To do so you need to let Next.js know what pages to generate, by specifying all codes.
export async function getStaticPaths() {
// you don't need here a code from current route
// but you need to specify all known post codes
return {
paths: [
{ params: { code: '1' } },
{ params: { code: '2' } },
{ params: { code: '3' } },
]
fallback: false
}
}
You would need to re-build app every time you change the posts.
Use getServerSideProps if you don't want to re-build project every time. Then the data would be fetched at request time. You can't export it because it requires Node.js server.
I am trying to set query params with Vue-router when changing input fields, I don't want to navigate to some other page but just want to modify url query params on the same page, I am doing like this:
this.$router.replace({ query: { q1: "q1" } })
But this also refreshes the page and sets the y position to 0, ie scrolls to the top of the page. Is this the correct way to set the URL query params or is there a better way to do it.
Edited:
Here is my router code:
export default new Router({
mode: 'history',
scrollBehavior: (to, from, savedPosition) => {
if (to.hash) {
return {selector: to.hash}
} else {
return {x: 0, y: 0}
}
},
routes: [
.......
{ path: '/user/:id', component: UserView },
]
})
Here is the example in docs:
// with query, resulting in /register?plan=private
router.push({ path: 'register', query: { plan: 'private' }})
Ref: https://router.vuejs.org/en/essentials/navigation.html
As mentioned in those docs, router.replace works like router.push
So, you seem to have it right in your sample code in question. But I think you may need to include either name or path parameter also, so that the router has some route to navigate to. Without a name or path, it does not look very meaningful.
This is my current understanding now:
query is optional for router - some additional info for the component to construct the view
name or path is mandatory - it decides what component to show in your <router-view>.
That might be the missing thing in your sample code.
EDIT: Additional details after comments
Have you tried using named routes in this case? You have dynamic routes, and it is easier to provide params and query separately:
routes: [
{ name: 'user-view', path: '/user/:id', component: UserView },
// other routes
]
and then in your methods:
this.$router.replace({ name: "user-view", params: {id:"123"}, query: {q1: "q1"} })
Technically there is no difference between the above and this.$router.replace({path: "/user/123", query:{q1: "q1"}}), but it is easier to supply dynamic params on named routes than composing the route string. But in either cases, query params should be taken into account. In either case, I couldn't find anything wrong with the way query params are handled.
After you are inside the route, you can fetch your dynamic params as this.$route.params.id and your query params as this.$route.query.q1.
Without reloading the page or refreshing the dom, history.pushState can do the job.
Add this method in your component or elsewhere to do that:
addParamsToLocation(params) {
history.pushState(
{},
null,
this.$route.path +
'?' +
Object.keys(params)
.map(key => {
return (
encodeURIComponent(key) + '=' + encodeURIComponent(params[key])
)
})
.join('&')
)
}
So anywhere in your component, call addParamsToLocation({foo: 'bar'}) to push the current location with query params in the window.history stack.
To add query params to current location without pushing a new history entry, use history.replaceState instead.
Tested with Vue 2.6.10 and Nuxt 2.8.1.
Be careful with this method!
Vue Router don't know that url has changed, so it doesn't reflect url after pushState.
Actually you can push query like this: this.$router.push({query: {plan: 'private'}})
Based on: https://github.com/vuejs/vue-router/issues/1631
Okay so i've been trying to add a param to my existing url wich already have params for a week now lol,
original url: http://localhost:3000/somelink?param1=test1
i've been trying with:
this.$router.push({path: this.$route.path, query: {param2: test2} });
this code would juste remove param1 and becomes
http://localhost:3000/somelink?param2=test2
to solve this issue i used fullPath
this.$router.push({path: this.$route.fullPath, query: {param2: test2} });
now i successfully added params over old params nd the result is
http://localhost:3000/somelink?param1=test1¶m2=test2
If you are trying to keep some parameters, while changing others, be sure to copy the state of the vue router query and not reuse it.
This works, since you are making an unreferenced copy:
const query = Object.assign({}, this.$route.query);
query.page = page;
query.limit = rowsPerPage;
await this.$router.push({ query });
while below will lead to Vue Router thinking you are reusing the same query and lead to the NavigationDuplicated error:
const query = this.$route.query;
query.page = page;
query.limit = rowsPerPage;
await this.$router.push({ query });
Of course, you could decompose the query object, such as follows, but you'll need to be aware of all the query parameters to your page, otherwise you risk losing them in the resultant navigation.
const { page, limit, ...otherParams } = this.$route.query;
await this.$router.push(Object.assign({
page: page,
limit: rowsPerPage
}, otherParams));
);
Note, while the above example is for push(), this works with replace() too.
Tested with vue-router 3.1.6.
Here's my simple solution to update the query params in the URL without refreshing the page. Make sure it works for your use case.
const query = { ...this.$route.query, someParam: 'some-value' };
this.$router.replace({ query });
My solution, no refreshing the page and no error Avoided redundant navigation to current location
this.$router.replace(
{
query: Object.assign({ ...this.$route.query }, { newParam: 'value' }),
},
() => {}
)
this.$router.push({ query: Object.assign(this.$route.query, { new: 'param' }) })
You could also just use the browser window.history.replaceState API. It doesn't remount any components and doesn't cause redundant navigation.
window.history.replaceState(null, '', '?query=myquery');
More info here.
For adding multiple query params, this is what worked for me (from here https://forum.vuejs.org/t/vue-router-programmatically-append-to-querystring/3655/5).
an answer above was close … though with Object.assign it will mutate this.$route.query which is not what you want to do … make sure the first argument is {} when doing Object.assign
this.$router.push({ query: Object.assign({}, this.$route.query, { newKey: 'newValue' }) });
To set/remove multiple query params at once I've ended up with the methods below as part of my global mixins (this points to vue component):
setQuery(query){
let obj = Object.assign({}, this.$route.query);
Object.keys(query).forEach(key => {
let value = query[key];
if(value){
obj[key] = value
} else {
delete obj[key]
}
})
this.$router.replace({
...this.$router.currentRoute,
query: obj
})
},
removeQuery(queryNameArray){
let obj = {}
queryNameArray.forEach(key => {
obj[key] = null
})
this.setQuery(obj)
},
I normally use the history object for this. It also does not reload the page.
Example:
history.pushState({}, '',
`/pagepath/path?query=${this.myQueryParam}`);
The vue router keeps reloading the page on update, the best solution is
const url = new URL(window.location);
url.searchParams.set('q', 'q');
window.history.pushState({}, '', url);
With RouterLink
//With RouterLink
<router-link
:to="{name:"router-name", prams:{paramName: paramValue}}"
>
Route Text
</router-link>
//With Methods
methods(){
this.$router.push({name:'route-name', params:{paramName: paramValue}})
}
With Methods
methods(){
this.$router.push({name:'route-name', params:{paramName, paramValue}})
}
This is the equivalent using the Composition API
<script setup>
import { useRouter } from 'vue-router'
const router = useRouter()
router.push({ path: 'register', query: { plan: 'private' }})
</script>
You can also use the Vue devtools just to be sure that it's working as expected (by inspecting the given route you're on) as shown here: https://stackoverflow.com/a/74136917/8816585
Update
That will meanwhile mount/unmount components. Some vanilla JS solution is still the best way to go for that purpose.