Watch for URL query parameter in Vuex store - vuejs2

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
}
}

Related

Computed value based on component's created method

In my component I have acreated method where I make a request and then I want to use the data I get for a computed property.
computed: {
...mapState(["pages"]),
pageContent() {
let slug = this.$route.params.slug ? this.$route.params.slug : 'home' ;
let page = this.pages.find(page => page.slug == slug);
return page.content;
}
},
methods: {
...mapActions(['setPagesAction'])
},
created() {
this.setPagesAction();
}
The problem is that created is executed after the computed property pageContent is read, so it is undefined.
How can I make pageContent get the data from created ?
Solution
As you know, computed property will update itself when the dependent data changes. ie when this.pages is assigned in vuex. So
You have mainly three options.
Option 1
Set a variable so that till the time page content is being loaded, a loading spinner etc is being shown.
Option 2
If you are using vue-router, then use the beforeRouteEnter guard to load the data.
like here
Option 3
Load data initially (when app starts) and add it to vuex. (you can use vuex modules for seperating data store if needed)

vue.js change query location NavigationDuplicated

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 : {}
}

Using one vuex module store in multiple sibling components

I have one global state with some modules.
now i have vue components for various parts of my page.
i have everything setup so /foo uses the foo store (this works).
the created method loads data from an API and writes it to the store
now i have /foo/bar as another (sibling) component, but it needs to access the same store as /foo, but i can't get it to work.
if i enter /foo/bar/ in the URL, there is nothing in the store.
but if i switch to /foo, and then back to /foo/bar, the data is in the store and being output correctly
I've tried registering /foo/bar as a child, which seemed to have no effect (and actually it's not really a child, but just another page with the same data..)
I also tried
state: {
...mapState([
'foo'
)]
}
in /foo/bar, but that doesn't seem to be the right way either
what is the best practice to
load data from API on created on any of a specified set of pages
access said data on any of those pages (i.e. sharing the same store)
i've tried all day to find a solution, but it seems I didn't understand something.
thanks for your help :)
EDIT
actually, while i read my question again, i think my whole problem is the data not being loaded (because the created method is not called). how can i make sure this happens on any page using the store and just once? i can't just write an api call in every created method, can i?
Well, I think just to summarize your problem could be called like you're not being able to access the same state between two different componentes.
What I do normally is that I make an API call from one component inside the method beforeMount, that will guarantee that once my component is created, the data will be available to be used.
Furthermore, after calling the api, I update my state so after that I can call it from everywhere.
One thing that you have to take care with is which component is loaded first?
If A is B's parent, then you should load data inside A.
However, if A and B are siblings, then you should load data inside both of them because you can access first either Component A or B, then you don't know when the data is going to be available. In that case, I would load the data in both of the components.
Also, add cache to your server so you don't need to load the same data again.
For example:
State
{
data: {}
}
Component A
export default {
name: 'Batch',
beforeMount() {
this.getDataFromAPI();
},
methods: {
// getDataFromAPI will store its return inside data with a mutation
...mapActions(['getDataFromAPI']),
randomMethod() {
// Now I can Use my state
const data = this.$store.state.data;
}
}
};
Component B
export default {
name: 'Batch',
methods: {
randomMethodB() {
// If component A was loaded first than component B and A is B's parent, then the state will be accessible in the same manner and it should be populated
const data = this.$store.state.data;
}
}
};
Actions
const getDataFromAPI = ({ commit }) => new Promise((resolve, reject) => {
// Call server
const data = await callServer();
commit('updateMyStateWithData');
resolve(data);
});
export default {
getDataFromAPI
}
Mutations
const mutations = {
updateMyStateWithData(state, newData) {
state.data = newData;
}
}
export default mutations;
Another thing that I do is to define getters, that way is a good approach to load data once, and inside the getter you update the data to return only the things that your UI needs.
I hope that it helps!

How to stop reloading the components in vue router?

I tried to find some article on it but couldn't find any.
I am using vue router and loading data via axios in created hook. The problem is, I don't want to call the created hook every time user visits to next route and come back. So when user click back, currently the created hook runs which reloads the data from database. What I want is to block re rendering of data and use already loaded previous components.
Thank you.
You can create variable e.g. initialState to maintain the data and if that is empty query the database otherwise take from the variable.
const initialState = {
counter: 0,
};
export default {
name: 'ComponentName',
created() {
initalState.counter += 1;
console.log(initalState.counter);
}
}
If you refresh the browser then variable will be reset to origin in that case you can use localstorage

vue2: can not find a proper way to initialize component data by ajax

I have a component whose data is initialized by ajax. I know vue.js has provide several lifecycle hooks: Lifecycle-Diagram. But for ajax to initialize the data, which hook(beforeCreate, create, mounted, etc) is the best place to do it:
hook_name: function() {
ajaxCall(function(data) {
me.data = data;
});
}
Currently, i do it in mounted, making it to re-render the component. But i think we should get the data before the first render. Can someone figure out the best way to do it?
If you want to initialize your component with data you receive from a request, created() would be the most appropriate hook to use but it is a request, it might not resolve by the end of created or even mounted() (when even your DOM is ready to show content!).
So do have your component initialized with empty data like:
data () {
return {
listOfItems: [],
someKindOfConfig: {},
orSomeSpecialValue: null
}
}
and assign the actual values when you receive them in your created hook as these empty data properties would be available at that point of time, like:
created () {
someAPICall()
.then(data => {
this.listOfItems = data.listOfItems
})
/**
* Notice the use of arrow functions, without those [this] would
* not have the context of the component.
*/
}
It seems like you aren't using (or aren't planning to use) vuex but I'd highly recommend you to use it for for managing your data in stores. If you use vuex you can have actions which can make these api calls and by using simple getters in your component you would have access to the values returned by the request.