Vuex store unable to update component data - vue.js

I am trying to create a HN clone in 2 panes, but for some reason my vuex store is unable to update the component data.
This is the project link since there are too many files involved.
https://github.com/karansinghgit/hn-vue
This is what it looks like. My aim is to click on one of the articles on the left, and display the hn article with its comments on the right.
So far, I have understood that I need to use vuex to share data but the sharing is not taking place.
It just displays a function signature, when I want it to display the article ID.

The problem is in your store.js file. You are setting the default state for currentStory to Number. Setting it to an actual number instead should solve your problem:
export const store = new Vuex.Store({
state: {
currentStory: 0
},
mutations: {
setCurrentStory(state, ID) {
state.currentStory = ID
}
},
getters: {
currentStory: state => state.currentStory
}
})
Additionally, in story.vue, it is unnecessary to specify storyID in the data as you already have it as a computed property (there might be an error thrown for duplicate keys)

Related

How can I keep the state saved when I coming to same route component from another in vuejs?

I have a claims dropdown and function called on change of dropdown value is like this:
changeDdVal(v) {
this.$store.dispatch('getUserRoles').then(userRoles => {
if (userRoles.admin) {
this.selectedYear = new Date().getFullYear();
switch (v) {
case 1:
this.dropValue = "salesClaimCount";
this.headerDropdownClaim = "sales";
this.tableHeadData = this.salesTableHeadData;
this.api.filterData = "getSalesClaimFilterDataForApprover";
this.$router.push({name: this.routeName, params: {approverClaimType: this.claimTableData.claimStatus, dropType: this.headerDropdownClaim, userRole: this.role}})
.catch(() => {});
this.yearChange(this.selectedYear);
break;
case 2:
this.dropValue = "staffingClaimCount";
this.headerDropdownClaim = "staffing";
this.tableHeadData = this.placementTableHeadData;
this.api.filterData = "getStaffingClaimFilterDataForApprover";
this.$router.push({name: this.routeName, params: {approverClaimType: this.claimTableData.claimStatus, dropType: this.headerDropdownClaim, userRole: this.role}})
.catch(() => {});
this.yearChange(this.selectedYear);
break;
}
}
})
}
And this yearChange function is taking care of calling api with the selected year and display data accordingly. I am calling this changeDdVal function on created state. When the data is displayed and if I click on any data, I am routing to detail view of that particular record.
Now the problem is when I change the year to any other from current year and click on that particular year data and come back to dashboard using "$router.go(-1)", my states resets i.e the year changes to current again but I want to remain in the selected year.
I know this is happening because of the "this.selectedYear = new Date().getFullYear();" and I can use vuex to store the state instead of this.
But in my case I want to reset the state on drop change and save the state if I come from the detail view of that record of selected year.
For example, by default the year is 2022 and I change it to 2021 the data is shown of 2021 and I click on any record of 2021 and use $router.go(-1) on that I should be in 2021. And if I have selected 2021 year and change the drop from sales to placement the year should reset to 2022.
this is my year change function
yearChange() {
this.axiosCancelToken.cancel("cancel requested")
this.changeYear = false;
this.firstLoad = true;
this.sortBy = "claimId";
this.sortType = "descending";
this.handlePagination(this.paginationOptions);
this.mavePaginationList(false);
this.fetchData();
var dataApi = new FormData();
dataApi.append("year", this.selectedYear);
api
.post(this.api.filterData, {data: dataApi})
.then((res) => {
//resposne
})
},
According to your question-
You need to keep some values saved when changing the route and some values to refresh as per API requests. (keep-alive won't do much in that use case.)
the values which need to persist are either a form variable (select dropdown) or some manual variable (pagination number).
As you said you can't use Vuex because of some problems, My guess, is that Vuex and vuex-map-fields together should ease the process of your problem.
vuex-map-fields is a good way to enable two-way data binding for fields saved in a Vuex store.
If I take your question as an example-
Suppose at your Home.vue you change the dropdown value, and on its changes, an API request gets fired and brings some data which you display on your component, and you can select any data which will lead to a new route, let's say About.vue and when you come back to Home.vue the year you selected should persist.
So, you can do this by like this-
Install the vuex-map-fields and configure it properly.
Create a property in your Vuex state of name, "selectedYear", like this-
import Vuex from "vuex";
// Import the `getField` getter and the `updateField`
// mutation function from the `vuex-map-fields` module.
import { getField, updateField } from "vuex-map-fields";
Vue.use(Vuex);
const store = new Vuex.Store({
state: {
selectedYear: null
},
getters: {
// Add the `getField` getter to the
// `getters` of your Vuex store instance.
getField
},
mutations: {
// Add the `updateField` mutation to the
// `mutations` of your Vuex store instance.
updateField
}
});
In your Vue template, use this Vuex state property to get and mutate the selectedYear.
<select v-model="selectedYear" #change="callAPI()">
<option>2020</option>
<option>2021</option>
<option>2022</option>
</select>
import { mapFields } from "vuex-map-fields";
export default {
name: "Home",
computed: {
// The `mapFields` function takes an array of
// field names and generates corresponding
// computed properties with getter and setter
// functions for accessing the Vuex store.
...mapFields(["selectedYear"]),
},
}
And when you want to reset the dropdown state, simply do this-
this.selectedYear = null;
It will update in the Vuex state as well.
In that way, you should notice that when you switch the route, the selected box state will persist because this is bonded with the store.
In the same fashion, you can keep the state of pagination, and another variable you want.
The benefit of using vuex-map-field is that you don't need to write getter and setter functions manually to contact the state.
Note- If you want to persist your Vue state between page reloads also, then use vuex-persistedstate with Vuex.
As your code depends on multiple concepts, I cannot provide the snippet here, but I created a fiddle and try to reproduce the problem, you can look at it, and modify it according to your case and make sure things work.
In the fiddle, I used the same problem mentioned in your question (but the data and API request is dummy), and you should see when you change the routes (from Home.vue to About.vue), the year would persist but when reload the page it won't because I didn't use vuex-persistedstate.
Here it is- https://codesandbox.io/s/vue-router-forked-mdvdhd?file=/src/views/Home.vue:935-1181

Reuse components with different Vuex stores in NuxtJS - Create dynamic/multiple VueX store instances

I have a vue.js/nuxt.js component in my UI that displays news based on a backend which can be queried with selectors (e.g. news-type1, news-type2).
I want to add a second instance of that component which uses exactly the same backend, but allows the user to use a few different selectors (e.g. news-type3, news-type4). The UI kinda works dashboard-like. Implementing that distinction in the .vue component file is no problem (just accept some props and display stuff conditionally to the user), but:
How do I reuse the vuex store? The code of the store for the new card stays exactly the same since the same backend is used. But I can't use the same instance of the store because the selectors and the loaded news should be stored per component and should not be shared between them. Surprisingly I haven't been able to find any easy solutions for that in nuxt. I thought this would be a common use case.
MWE for my use case:
/** Vuex store in store/news.js **/
export const state = () => ({
// per default only news-type1 is selected, but not news-type2. the user can change that in the UI
currentSelectors: ['news-type1'],
news = [] // object array containing the fetched news
});
export const mutations = {
// some very simple mutations for the stae
setSelectors (state, data) {
state.currentSelectors = data;
},
setNews (state, data) {
state.news = data;
}
}
export const actions = {
// simplified get and commit function based on the currentSelectors
async loadNews ({ commit, state }) {
const news = await this.$axios.$get(`/api/news/${state.currentSelectors.join(',')}`);
commit('setNews', news);
// ... truncated error handling
},
// Helper action. In comparison to the mutation with the same name, it also calls the load action
async setSelectors ({ commit, dispatch }, selectors) {
commit('setSelectors', selectors);
dispatch('loadNews');
},
};
In my news-card.vue I simply map the two states and call the two actions loadNews (initial load) and setSelectors (after user changes what news to show in the UI). This should stay the same in both instances of the card, it just should go to different store instances.
My current alternative would be to simply copy-paste the store code to a new file store/news-two.js and then using either that store or the original store depending on which prop is passed to my news-card component. For obvious reasons, that would be bad practice. Is there a better complicated alternative that works with nuxt?
All related questions I have found are only for Vue, not for nuxt vuex stores: Need multiple instances of Vuex module for multiple Vue instances or How to re-use component that should use unique vuex store instance.

How to update multiple dashboard components on click - Vue.js

I've found it very difficult to find help online with this issue as no examples seem to match my use case. I'm basically wanting to check if I am on the right track in my approach.I have a single page Vue app:
Each row on the right is a component. On the left are listed three data sets that each possess values for the fields in the dashboard. I want it to be so that when you click on a dataset, each field updates for that set.
So if you click on 'Beds', the title becomes 'Beds' and all the fields populate the specific data for beds.
I want to do this without having separate pages for each dataset since that would seem to defeat the point of using a reactive framework like Vue. Only the embedded components should change, not the page.
I have installed Vue Router and have explored using slots and dynamic components but it is very hard to understand.
If someone experienced in Vue could just let me know the right broad approach to this I then know what I need to look into, at the moment it is difficult to know where to start. Thank you
You can use Vuex for that purpose.
Add property to the state, dataset for example. And mutation to change it. Every component on the right side should use that this.$store.state.dataset (or through mapState) for its own purposes. So when you're selecting one of listed datasets on the left side, it will mutate dataset in store with its own data.
Something like that:
store (there are alternate version, where we can use getter, but its little bit more complicated for just an example).
import Vue from 'vue';
const store = new Vuex.Store({
state: {
dataset: {}
},
mutations: {
setDataset(state, payload) {
Vue.set(state, 'dataset', payload);
}
}
});
one of the right side component
computed: {
dataset() {
return this.$store.state.dataset;
},
keywords() {
return this.dataset.keywords;
},
volume() {
return this.dataset.volume;
}
}
left menu
template:
{{dataset.title}}
code:
data() {
return {
datasets: [{
id: 1,
title: 'Sofas',
keywords: ['foo'],
volume: 124543
}]
}
},
methods: {
changeDataset(dataset) {
this.$store.commit('setDataset', dataset);
}
}
datasets is your data which you're loading from server.
BUT You can use some global variable for that, without Vuex. Maybe Vue observable, added in 2.6.

How to access Vuex modules mutations

I read thorugh his documentation from vue but I didn't find anything about how to actually access specific module in the store when you have multiple modules.
Here is my store:
export default new Vuex.Store({
modules: {
listingModule: listingModule,
openListingsOnDashModule: listingsOnDashModule,
closedListingsOnDashModule: listingsOnDashModule
}
})
Each module has its own state, mutations and getters.
state can be successfully accessed via
this.$store.state.listingModule // <-- access listingModule
The same is not true for accessing mutations cause when I do this
this.$store.listingModule.commit('REPLACE_LISTINGS', res)
or
this.$store.mutations.listingModule.commit('REPLACE_LISTINGS', res)
I get either this.$store.listingModule or this.$store.mutations undefined error.
Do you know how should the module getters and mutations be accessed?
EDIT
As Jacob brought out, the mutations can be accessed by its unique identifier. So be it and I renamed the mutation and now have access.
here is my mutation:
mutations: {
REPLACE_OPEN_DASH_LISTINGS(state, payload){
state.listings = payload
},
}
Here is my state
state: {
listings:[{
id: 0,
location: {},
...
}]
}
As I do a commit with a payload of an array the state only saves ONE element.
Giving in payload array of 4 it returns me back array of 1.
What am I missing?
Thanks!
It's a good idea, IMHO, to call vuex actions instead of invoking mutations. An action can be easily accessed without worrying about which module you are using, and is helpful especially when you have any asynchronous action taking place.
https://vuex.vuejs.org/en/actions.html
That said, as Jacob pointed out already, mutation names are unique, which is why many vuex templates/examples have a separate file called mutation-types.js that helps organize all mutations.
re. the edit, It's not very clear what the issue is, and I would encourage you to split it into a separate question, and include more of the code, or update the question title.
While I can't tell why it's not working, I would suggest you try using this, as it can resolve two common issues
import Vue from 'vue'
//...
mutations: {
REPLACE_OPEN_DASH_LISTINGS(state, payload){
Vue.$set(state, 'listings', [...payload]);
},
}
reactivity not triggered. Using Vue.$set() forces reactivity to kick in for some of the variables that wouldn't trigger otherwise. This is important for nested data (like object of an object), because vue does not create a setter/getter for every data point inside an object or array, just the top level.
rest destructuring. Arrays: [...myArray] Objects: {...myObj}. This prevents data from being changed by another process, by assigning the contents of the array/object as a new array/object. Note though that this is only one level deep, so deeply nested data will still see that issue.

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!