vue.js change query location NavigationDuplicated - vue.js

currently have query param id=2&p=3
want to change this with vue-router
tried :
this.$router.push({query:{id:'2',p:'4'}});
but throws NavigationDuplicated
weird ..
how to change just the query, to trigger watch.

The error will be thrown only if your params the same, so you could just check your params before push or replace. Also, you could use async/await or then/catch with there methods, here is an example:
try {
if (/* id or p have been changed in this.$route.query */) {
await this.$router.push({query:{id:'2',p:'4'}});
}
} catch (err) {
...
}

I had the same issue of getting a "NavigationDuplicated" error on adjusting my query. In my case the problem was because on page load I was naively getting the query object directly from the router. I loaded it like:
beforeRouteEnter (to, from, next) {
store.dispatch('setQuery', to.query);
next();
}
where setQuery would set my vuex store variable query.
The issue is then that this object just points to the query inside the route object, and making changes to it changes directly route.query but the url doesn't react to this change. If you then do something like:
watch: {
query (query) {
this.$router.push({query: query})
}
}
you will get a "NavigationDuplicated" error as the new "query" will be identical to the old "query" since you are just passing the pointer to the original object.
To avoid this issue you can do some sort of deep copy (e.g. JSON.parse(JSON.stringify(to.query))) to make sure you are not directly modifying the route.query when you adjust the query.

if you create button and want to router push, just convert those button to <router-link :to="routeObject" />. Router-link will not react if destination route = current route, so it won't show error navigationDuplicate
note:
routeObject : {
path : '/search',
query : {}
}

Related

How to stop Vue.js 3 watch() API triggering on exit

I have implemented a watch within a Vue component that displays product information. The watch watches the route object of vue-router for a ProductID param to change. When it changes, I want to go get the product details from the back-end API.
To watch the route, I do this in Product.vue:
import { useRoute } from 'vue-router'
export default {
setup() {
const route = useRoute();
async function getProduct(ProductID) {
await axios.get(`/api/product/${ProductID}`).then(..do something here)
}
// fetch the product information when params change
watch(() => route.params.ProductID, async (newID, oldID) => {
await getProduct(newId)
},
//watch options
{
deep: true,
immediate: true
}
)
},
}
The above code works, except that if a user navigates away from Product.vue, for example using the back button to go back to the homepage, the watch is triggered again and tries to make a call to the API using undefined as the ProductID (becaues ProductID param does not exist on the homepage route) e.g. http://localhost:8080/api/product/undefined. This causes an error to be thrown in the app.
Why does the watch trigger when a user has navigated away from Product.vue?
How can this be prevented properly? I can do it using if(newID) { await getProduct(newId) } but it seems counterintuitive to what the watch should be doing anyway.
UPDATE & SOLUTION
Place the following at the top replacing the name for whatever your route is called:
if (route.name !== "YourRouteName") {
return;
}
That will ensure nothing happens if you are not on the route you want to watch.
I ran into the same problem. Instead of watching the current route, use vue-router onBeforeRouteUpdate, which only gets called if the route changed and the same component is reused.
From https://next.router.vuejs.org/guide/advanced/composition-api.html#navigation-guards:
import { onBeforeRouteLeave, onBeforeRouteUpdate } from 'vue-router'
import { ref } from 'vue'
export default {
setup() {
// same as beforeRouteLeave option with no access to `this`
onBeforeRouteLeave((to, from) => {
const answer = window.confirm(
'Do you really want to leave? you have unsaved changes!'
)
// cancel the navigation and stay on the same page
if (!answer) return false
})
const userData = ref()
// same as beforeRouteUpdate option with no access to `this`
onBeforeRouteUpdate(async (to, from) => {
// only fetch the user if the id changed as maybe only the query or the hash changed
if (to.params.id !== from.params.id) {
userData.value = await fetchUser(to.params.id)
}
})
},
}
watch registers the watcher inside an vue-internal, but component-independent object. I think it's a Map. So destroying the component has no effect on the reactivity system.
Just ignore the case where newID is undefined, like you already did. But to prevent wrapping your code in a big if block just use if(newID === undefined)return; at the beginning of your callback. If your ids are always truthy (0 and "" are invalid ids) you can even use if(!newID)return;.
well, in your use case the best approach would be to have a method or function which makes the api call to the server, having watch is not a really good use of it, because it will trigger whenever route changes and you do not want that to happen, what you want is simply get the productID from route and make the api call,
so it can be done with getting the productID in the created or mounted and make the api call!

Nuxt js : How to display result from a promise in the template?

I try to display {{ bgColor }} in my template.
But it only displays [object Promise]
const { getColorFromURL } = require('color-thief-node')
export default {
data() {
return {
api_url: process.env.strapiBaseUri,
bgColor: this.updateBgColor(this.project),
}
},
props: {
project: Object,
},
methods: {
updateBgColor: async function(project){
return await getColorFromURL(process.env.strapiBaseUri+this.project.cover.url).then((res) => {
const [r, g, b] = res
console.log( `rgb(${r}, ${g}, ${b})` )
return `rgb(${r}, ${g}, ${b})`
})
}
}
}
The function looks to work, as I get the result on console.log
rgb(24, 155, 97)
I think I'm lost between methods, the plugin and the use of an async function.
Any help appreciated !
https://forum.vuejs.org/t/render-async-method-result-in-template/36505/3
Render is sync. You should call this method from created or some other suitable lifecycle hook and save the result in component’s data, then render that data.
If not lifecycle any event like 'click' will be good also
The best thing you probably can do is to change return statement to assign statement
Instead of return 'rgb(${r}, ${g}, ${b})' try this.something = rgb(${r}, ${g}, ${b})' (of course you have to define something in data object). Then just render {{ something }} in your template. Reactivity system will do the rest
Soo all your Vue logic is good, but promise is never resolved cause of :
canvas-image.js?14a5:35 Uncaught (in promise) DOMException: Failed to execute 'getImageData' on 'CanvasRenderingContext2D': The canvas has been tainted by cross-origin data.
I've copied it to my local project and I am getting this error
How to fix getImageData() error The canvas has been tainted by cross-origin data?
You won't be able to draw images directly from another server into a canvas and then use getImageData. It's a security issue and the canvas will be considered "tainted".
Don't know if it will solve your problem but you can download this image. And put to for example inside assets. Then just required it from there. And it will work

Watch for URL query parameter in Vuex store

I am using Nuxt.js with Vuex and I would like to trigger a mutation when somebody enters in my web with a certain parameter (ex: https://example.com/?param=abc), and pass the parameter to a state.
I tried to check the documentation of the watchQuery property https://nuxtjs.org/api/pages-watchquery, but there’s no examples about how to do this, I just found this How to watch on Route changes with Nuxt and asyncData but I can’t see any way of how to write an action in Vuex store with watchQuery.
I tried writing:
actions: {
watchQuery: true,
asyncData ({ query, app }) {
const { start } = query
const queryString = start ? `?start=${start}` : ''
return app.$axios.$get(`apps/${queryString}`)
.then(res => {
commit('setParam',res.data);
})
},
}
But that syntax is not allowed.
Any help would be welcome, thanks in advance!
From my understanding watchQuery sets a watcher for query string, meaning it's waiting for the query to change while the page is already rendered making it possible to call methods like asyncData() again.
Since you only want to save a certain parameter when the user enters the page and then pass the paramater to a state you just need to move your asyncData method to a page from which you want to get the parameter, you will also need to extract store and query from the context automatically passed into asyncData and then using the store and query save the query parameter into your state.
Here is a simple demonstrantion
// Your page from which you want to save the param
export default {
asyncData({store, query}) { // here we extract the store and query
store.state.somethingForSavingTheParam = query.nameOfTheParamYouWantToSave
// instead of using store.state you could use store.commit(...) if that's what you want
}
}

GraphQL query using React Native AsyncStorage

I'm trying to build an component, using React Native and Apollo Client that execute the following Query:
query getTable($tableId:String!){
getTable(id:$tableId){
users{
name
imageURL
}
}
So, as you the query above have a variable called tableId and that value is stored using the AsyncStoragemethod. Is there a way that I can get the value from the AsyncStorage and use it as query variable.
I tried to do the following but it didn't work:
graphql(Query, {
options: {
tableId: await AsyncStorage.getItem('table'),
},
})(MyComponent);
In my opinion, the preferred way of doing this would look like this:
Call AsyncStorage.getItem() in a parent component and then pass the result down as a prop to MyComponent. As illustrated in the docs, options can be a function instead of an object, in which case it gets the component's props passed to it as its first argument. So, assuming the prop is called tableId, your HOC would look like this:
graphql(Query, { options: ({ tableId }) => ({ variables: { tableId } }) })
Alternatively, you could set up the query with some default value:
graphql(Query, { options: { variables: { tableId : 'foo' } } })
You would call AsyncStorage.getItem() in your component's componentDidMount() method and assign the result to your component's state. Your component will have a data prop available to it when it renders. You can call this.props.data.refetch({ tableId: this.state.tableId }) from your render function to force the query to update with the newly available id.
I think that's a lot less clean than the first option, and will require additional logic to keep your component from calling refetch or rerendering unnecessarily... but it should still work if for some reason you don't want to change the parent component.

In Vue.js, how do you prevent navigation for a subroute?

The nice thing about beforeRouteLeave is that you can prevent navigating away under certain conditions.
I have a setup that uses a subroute to render part of the page. I would like a navigation guard on the subroute to prevent switching to another one if the data is not saved.
{
path: '/customers/view',
component: ViewCustomerShell,
children: [
{path: ':id', name: 'ViewCustomer', component: ViewCustomer}
]
},
So when I visit /customers/view/12 and make a change, if they try to load /customers/view/13, I want to pop up the usual confirmation and potentially stop navigation. Since beforeRouteLeave is not called in this situation, what is the recommended approach for preventing navigation? It seems that watching $route would be too late, because then the navigation has already occurred.
Note: As mentioned above, beforeRouteLeave is not called in this situation; it doesn't work.
Note: Using onbeforeunload doesn't work because it only triggers when the entire page changes.
I have also posted the same answer here.
Dynamic route matching is specifically designed to make different paths or URLs map to the same route or component. Therefor, changing the argument does not technically count as leaving (or changing) the route, therefor beforeRouteLeave rightly does not get fired.
However, I suggest that one can make the component corresponding to the route responsible for detecting changes in the argument. Basically, whenever the argument changes, record the change then reverse it (hopefully reversal will be fast enough that it gets unnoticed by the user), then ask for confirmation. If user confirms the change, then use your record to "unreverse" the change, but if the user does not confirm, then keep things as they are (do not reverse the reverse).
I have not tested this personally and therefor I do not gurantee it to work, but hopefully it would have cleared up any confusion as to which part of the app is responsible for checking what change.
I know that this post is very old. but it was the first one I found when looking for the same problem.
I have no idea if there is a better solution nowadays but for those who are looking for a solution, I can share mine:
1. Define a global state
let isRouteChangeBlocked: boolean = false;
export function blockRouteChange(set?: boolean): boolean {
if (arguments.length == 1) {
isRouteChangeBlocked = !!set;
return isRouteChangeBlocked;
}
return isRouteChangeBlocked;
}
2. Replace the route function
const originalPush = VueRouter.prototype.push;
VueRouter.prototype.push = function(location: RawLocation) {
if (blockRouteChange()) {
if (confirm("Du hast ungespeicherte Änderungen, möchtest du fortfahren?")) {
blockRouteChange(false);
return originalPush.call(this, location) as any;
}
return;
}
return originalPush.call(this, location) as any;
};
3. Set the state
#Watch("note.text")
private noteTextChanged() {
blockRouteChange(true);
}
This does exactly what I want. If nowadays there is a better solution, let me know. You can get the full runnable example here: https://github.com/gabbersepp/dev.to-posts/tree/master/blog-posts/vuejs-avoid-routes/code/example
You could use a $route object inside your component to watch if it changes and then raise up the confirmation modal... This will get called whenever your route changes!
const Baz = {
data () {
return { saved: false }
},
template: `
<div>
<p>baz ({{ saved ? 'saved' : 'not saved' }})<p>
<button #click="saved = true">save</button>
</div>
`,
watch: {
'$route': function () {
if (this.saved || window.confirm('Not saved, are you sure you want to navigate away?')) {
// do something ...
}
}
}