VueJS: $router.push not working with query parameters - vue.js

In my NuxtJS(v. 2.10.2) application, I have a URL like below where pid is a post's id.
/post?pid=5e4844e34202d6075e593062
This URL works fine and loads the post as per the value passed to the pid query parameter. However, user can add new post by clicking Add Post button on the application bar that opens a dialog. Once the user clicks add, a request to back-end server is made to save the request. And once successful, user is redirected to the new post using vue router push like below
.then(data => {
if (data) {
this.$router.push({ path: `/post?pid=${data.id}` });
}
})
The problem is, user is not redirected to the new post, only the query parameter pid is updated. I suspect VueJS does not acknowledge this as a different URL and hence does nothing.
How to fix this?
Update: As an alternative tried the syntax below but getting the same behavior.
this.$router.push({ path: "post", query: { pid: data.id } });

Say you have a component post.vue which is mapped with /post URL.
Now if you redirect the user to /post?pid=13, the post.vue component won't mount again if it's already mounted ie. when you are already at /post or /post?pid=12.
[1] In this case, you can put a watch on the route to know if the route has been changed.
watch: {
'$route.path': {
handler (oldUrl, newUrl) {
let PID = this.$route.query.pid
// fetch data for this PID from the server.
// ...
}
}
}
OR
[2] If the component post.vue is mapped with some route say /post.
You can also use the lifecycle -> beforeRouteUpdate provided by vue-router
beforeRouteUpdate (to, from, next) {
let PID = to.query.pid
// fetch data for this PID from the server.
// ...
next()
}

By changing the approach component data can be updated as per the new query string value. Here is how it can be done.
Rather than trying to push to the same page again with different query string. The query string pid itself can be watched for change and on update new data can be fetched and the component data can be updated. In NuxtJS(v. 2.10.2) apps, this can be achieved with watchQuery. watchQuery is a NuxtJS property which watches changes to a query strings. And once it detects the change, all component methods(asyncData, fetch, validate..) are called. You can read more https://nuxtjs.org/api/pages-watchquery/
As for the solution, pushing to the same page with new query string remains the same.
.then(data => {
if (data) {
this.$router.push({ name: 'post', query: { pid: data.id } });
}
})
However, on the page.vue, where the data is fetched from the server. We need to add watchQuery property.
watchQuery: ["pid"],
async asyncData(context) {
let response = await context.$axios.$get(
`http://localhost:8080/find/id/${context.route.query.pid}`
);
return { postData: response };
},
data: () => ({
postData: null
})
Now, everytime the query string pid will change asyncData will be called. And that is it. An easy fix to updating component data when the query string value change.

try this solution
.then(data => {
if (data) {
this.$router.push({ name: 'post', query: { pid: data.id } });
}
})
hints:
// with query, resulting in /register?plan=private
router.push({ path: 'register', query: { plan: 'private' } })

Use watchQuery property (https://nuxtjs.org/docs/2.x/components-glossary/pages-watchquery)
export default {
watchQuery: true,
data: () => ...
}

In case anybody was looking for this:
Query parameters specified as a string do not work when passed to a path parameter:
router.push({path: 'route?query=params'})
When you want you use them as a string, just pass the whole string as an argument, like so: router.push('route?query=params')
It'll then be automagically picked by router and navigation will happen.

try this :
.then(data => {
if (data) {
this.$router.push('/post?pid=' + data.id);
}
})
hope it works!!!

Related

Update data without refresh the page with Vue and axios

I have a page with 2 tabs (Questions and Data) made on Vue and Axios.
In first Tab, I fill the form and submit it - Save button v-on:click="save".
save: function() {
axios({
method: 'patch',
url: url,
data: this.data
})
.then(function (response) {
this.data = response.data;
}
In the second Tab(Data) I have the list of saved data:
mounted() {
axios
.get('/api/recommended-products/?patient_uuid=' + '{{patient.uuid}}')
.then(response => (this.data= response.data.results))
}
Now when I change answers in Questions Tab my list in Data Tab should change automatically. It changes if I refresh the page - mounted() works.
I tried to create updateList() function:
updateList: function() {
axios
.get('/api/recommended-products/?patient_uuid=' + '{{patient.uuid}}')
.then(response => (this.data= response.data.results))
}
and added it to save() function like:
save: function() {
axios({
method: 'patch',
url: url,
data: this.data
})
.then(function (response) {
this.data = response.data;
this.updateList();
}
The problem is that this way works other second time (sometime works sometimes not). So I just added location.reload(); to save() but I don't like this approach. Is it possible to update Data list without refreshing the page? What am I doing wrong with updateList() function?
In my opinion here you should use vuex and its getters.
You would then have only one request in all the application and the data would be automatically refreshed once updated in the state.
You can then access the data using a computed property which will be automatically re-rendered when the state is updated.
Here is an example using multiple tabs : https://codesandbox.io/s/vuex-axios-demo-forked-m0cqe4?file=/src/App.vue
In this example, i'm loading posts information through the JsonPlaceHolder API.
Every time the form is re send (using a function). The action of the store is called, then the state is updated which trigger the getter to re-render the data.
Note: i'm loading the first post into the mounted with a default value of 1 here.
save: function() {
axios({
method: 'patch',
url: url,
data: this.data
})
.then(function (response) {
this.data = […this.data, …response.data]
}
You have re rendered issue I think can can you try above solution
I think this might be helpful. Try to implement something like following.
async function() {
try{
await axios.post() // or any request
//action if success
//another action if success
...
} catch(error) {
//do something with error.
console.log(error)
}

Unable to dispatch an action after updating the route using router.push

When trying to dispatch an action after updating the route using this.$router, the action uses the previous route parameters instead of using the current route parameters.
Suppose I want to move to the /home and currently, I am on /about and in the store action called I want to access the route name, but after pushing the new route and then calling the action, it is using the previous route only and not the updated one.
this.$router.push('/home', this.$store.dispatch('called'))
Also, I tried using this.$router.push('/home').then(this.$store.dispatch('called')), but it gives undefined error.
You could pass the old and new queries from the method:
methods: {
changeRoute() {
const queryNew = { id: 1 }; // Create new query
const queryOld = { ...this.$route.query }; // Clone old query
this.$router.push({ path: '/home', query: queryNew }); // Navigate
this.$store.dispatch('called', { queryNew, queryOld }); // Call the action
}
}
The action signature should look like:
actions: {
called({ commit }, { queryNew, queryOld }) {
console.log(queryNew, queryOld);
}
}

Hide route params from the url with react-navigation

I am adapting a ReactNative mobile app to ReactNative Web. The app is done with react-navigation.
Currently, every time I set the params of a route (either through navigate or setParams), those params show in the URL. I end up with bad looking URLs like so:
http://localhost:19006/me/undefined?user=%5Bobject%20Object%5D
Either that or the URL contains irrelevant data for the user and generally looks messy.
Is there a way to not show the route params inside the URL?
You should re-consider if params is an appropriate place to have this data if you don't want in the URL. That you think that the URL contains irrelevant data is a sign that the data doesn't belong in the navigation state.
If you visit a screen directly from a URL, it should render the same as when you navigated to it porgrammatically. If you're passing something in params, it means that that information is needed for the screen to render correctly, and if you remove it from the URL, then that information is lost. Consider that it's possible to open the page directly from a URL, either on Web or with a deep link. If the required data isn't available, then it's not going to work.
In your case, seems like you're passing a full user object (maybe not, hard to say without code). Ideally, this object should be in your global store instead of params, and you should only pass the user id in params. Then you should gran the full object from your global store using that id, or trigger a data fetch if the objet isn't fetched yet (if needed).
You didn't specify the version of React Navigation in your question. In v5, you can customize how your params are stringified to URLs, and how they are parsed back using the stringify and parse options:
const linking = {
screens: {
Profile: {
path: 'user/:id',
parse: {
id: (id) => id.replace(/^#/, '')
},
stringify: {
id: (id) => `#{id}`,
},
},
},
};
This should help you with making URLs look prettier, and handle cases where params are not simple strings. However you can't omit params from the URL altogether since they are necessary data for the screen to render.
More details: https://reactnavigation.org/docs/configuring-links#passing-params
satya164's answer is definitely the proper path to follow. Just wanted to also post this solution as it is a direct answer to the question, even if not the most advisable.
import { getPathFromState} from "#react-navigation/native";
const linking = {
screens: {
...
},
getPathFromState(state, config){
let path = getPathFromState(state, config);
const index = path.indexOf("?")
if(index>=0){
path = path.substr(0, index);
}
return path;
}
};
NOTE: To use this solution you need to make sure that every object and function parameters are optional, otherwise if you reload the page you will get an error.
I removed every object and function from the url adding this custom getPathFromState to linkingOptions:
const linking = {
config: {
screens: {
...
}
},
getPathFromState: (state, options) => {
const cleanState = {
...state,
routes: state.routes.map(route => {
if(!route.params) {
return route
}
const cleanParams = {}
for(const param in route.params) {
const value = route.params[param]
if(typeof value !== "object" && typeof value !== "function") {
cleanParams[param] = value
}
}
return {
...route,
params: cleanParams,
}
}),
}
return getPathFromState(cleanState, options) //imported from #react-navigation/native
},
}
You can pass the parameters the component needs as a prop
like this
navigation.navigate('Details', {
itemId: 86,
otherParam: 'anything you want here',
});
then in Details Component
const { itemId , otherParam} = route.params;
Alternatively, if you use Redux/Mobx or any other global state management
You can pull the data from there and not pass it through the URL
then get data with the help connect or with useSelector hooks

Click on link changes the url but not the content/data on page

the story:
I am on product page #/product/7 and on the same page I have 4 more products that are similar to the one that is being viewed. All these products have links to their pages:
router-link(:to="{ name: 'product', params: { id: product.id }}" v-text='product.title').
the problem:
When I click on any of the product links, the url changes but the content remains the same. So, if I am on #/product/7 and click on #/product/8 the url only will change. If I navigate from /product/:id page and click on a product it takes me to the right page with proper content.
As you can see on screenshot, current product id is 15, but the content is the one from the id 7, as shown in url at the bottom while I was hovering over the Sleek Silk Shirt product in cart.
Any ideas how to fix this?
You have to update the data of products variable when you change the route as vue optimises page reloads and does not reload in your case if you are on same route.
You can adapt the approach: Fetching Before Navigation described in vue-router docs:
With this approach we fetch the data before actually navigating to the new route. We can perform the data fetching in the beforeRouteEnter guard in the incoming component, and only call next when the fetch is complete:
export default {
data () {
return {
product: {},
error: null
}
},
beforeRouteEnter (to, from, next) {
getProduct(to.params.id, (err, product) => {
if (err) {
// display some global error message
next(false)
} else {
next(vm => {
vm.product = product
})
}
})
},
// when route changes and this component is already rendered,
// the logic will be slightly different.
watch: {
$route () {
this.product = {}
getProduct(this.$route.params.id, (err, product) => {
if (err) {
this.error = err.toString()
} else {
this.product = product
}
})
}
}
}
I couldnt really internalise the above answer with 'getProduct', so to be put simply.
I am using a Store and I needed to watch the $route and when it changes I called my store to dispatch the api call.
watch: {
$route () {
this.$store.dispatch('fetchStudyStandards',
this.$route.params.standardID);
}
}
So basically watch the route and if it changes, re do your api call.

Params field is empty in $router.push

Consider this:
this.$root.$router.push({
path: '/dashboard',
params: { errors: 'error' },
query: { test: 'test' }
})
I use this in my component to redirect to another URL, and some error has occured. The problem is that when I want to access params field in dashboard component, it's empty. The query field works well. I'm trying to access it by this.$route.params.errors.
You can use params only with named paths (i think).
Example:
//route (in your router file should have "name")
{ path: '/errors', name: 'EXAMPLE', component: ... }
//navigating
this.$router.push({
name: 'EXAMPLE',
params: { errors: '123' }
});
Now it will have correct value in this.$route.params.
If you don't want to use named routes you can try this:
ES6
this.$root.$router.push({
path: `/dashboard/${error}`,
query: { test }
})
ES5
this.$root.$router.push({
path: '/dashboard/' + error,
query: { test: 'test' }
})
I faced the similar issue where in one of my views (component). I was trying to navigate (programmatically) from /foo/bar to /foo/bar/123, but the route param was not available later in the component. My relevant navigation code looked like below:
methods: {
save_obj() {
let vm = this;
// Make AJAX call to save vm.my_obj, and on success do:
let v = `${vm.$route.path}/${vm.my_obj.id}`;
console.log("Loading view at path: "+v);
vm.$router.push({ path: v });
},
...
}
It would print the expected log (e.g., Loading view at path: /foo/bar/112), however, the loading of data in the created() hook would not receive the value of route param. My failing created() code looked like below:
created: function() {
console.log("Loading object details.");
let vm = this;
let cid = vm.$route.params.id; // <---- This was the problem
vm.$http.get('api/'+cid)
.then(function (res) {
if (res.data.status == "OK") {
vm.my_obj = res.data.body;
} else {
vm.setStatusMessage(res.data.body);
}
})
.catch(function (error) {
console.log(error);
vm.setStatusMessage("Error: "+error);
});
}
The solution was indicated in the third note here quoted below :
Note: If the destination is the same as the current route and only
params are changing (e.g. going from one profile to another /users/1
-> /users/2), you will have to use beforeRouteUpdate to react to changes (e.g. fetching the user information).
I had to do the following in my component:
Change the line let cid = vm.$route.params.id; in created() to let cid = vm.course.id
and, add the following to the component:
beforeRouteUpdate(to, from, next) {
if (to.params.id) {
this.my_obj.id = to.params.id;
}
// Some other code specific to my app
next();
}
I hope this helps someone stuck with the similar issue.
If you want to send a parameter with a query parameter you can use that syntax like that
this.$router.push({
path: this.localePath(`/bookings/${requestReservation?.attributes?.booking_id}`),
query: { requestReservation: requestReservation }
})
You can access it on the next page like that
this.$route.query.requestReservation
If you want send it fro nuxt-link than its syntax like that
<nuxt-link
:to="{ path: '/bookings/'+ requestReservation.attributes.booking_id,
query: { requestReservation } }">
Booking
</nuxt-link>
You can access it on the next page same like previous
this.$route.query.requestReservation