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

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.

Related

Pinia: $reset alternative when using setup syntax

I have a pinia store created with setup syntax like:
defineStore('id', () => {
const counter = ref(0)
return { counter }
})
Everything has been working great with setup syntax because I can re-use other pinia stores.
Now, however, I see the need to re-use Pinia stores on other pages but their state needs to be reset.
In Vuex for example, I was using registerModule and unregisterModule to achieve having a fresh store.
So the question is: How to reset the pinia store with setup syntax?
Note: The $reset() method is only implemented for stores defined with the object syntax, so that is not an option.
Note 2: I know that I can do it manually by creating a function where you set all the state values to their initial ones
Note 3: I found $dispose but it doesn't work. If $dispose is the answer, then how it works resetting the store between 2 components?
You can use a Pinia plugin that adds a $reset() function to all stores:
On the Pinia instance, call use() with a function that receives a store property. This function is a Pinia plugin.
Deep-copy store.$state as the initial state. A utility like lodash.clonedeep is recommended for state that includes nested properties or complex data types, such as Set.
Define store.$reset() as a function that calls store.$patch() with a deep-clone of the initial state from above. It's important to deep-clone the state again in order to remove references to the copy itself.
// store.js
import { createPinia } from 'pinia'
import cloneDeep from 'lodash.clonedeep'
const store = createPinia()
1️⃣
store.use(({ store }) => {
2️⃣
const initialState = cloneDeep(store.$state)
3️⃣
store.$reset = () => store.$patch(cloneDeep(initialState))
})
demo
Feel free to read the article based on this answer: How to reset stores created with function/setup syntax
You can do this as suggested in the documentation here
myStore.$dispose()
const pinia = usePinia()
delete pinia.state.value[myStore.$id]

Vue composition API store

I was wondering if it was possible with the new Vue composition API to store all the ref() in one big object and use that instead of a vuex store. It would certainly take away the need for mutations, actions, ... and probably be faster too.
So in short, is it possible to have one place for storing reactive properties that will share the same state between different components?
I know it's possible to have reusable code or functions that can be shared between different components. But they always instantiate a new object I believe. It would be great if they would depend on one single source of truth for a specific object. Maybe I'm mixing things up...
You can use reactive instead of ref.
For eg.
You can use
export const rctStore = reactive({
loading: false,
list: [],
messages: []
})
Instead of
export const loading = ref(false)
export const list = ref([])
export const messages = ref([])
Cheers!

Is it always bad to use vue-component data as an object?

According to the official vue.js documentation, component data must be a function.
In my situation, i want to get data with an axios call and store this data in a component.
The problem is, if a declare this data as a function as the documentation advice to, every time i will need this component it will do a new axios call.
My main goal using vue-components was to minimize axios calls by re-using components-data through my vues, to improve performance.
Am i wrong to use vue-components in this situation?
EDIT: here's some code to clarify my question:
What the documentation recommend to do (data is a function which returns data, which means it will do the axios call everytime you want to use the data i guess.):
Vue.component('user-infos', {
data() {
userInfos = axios.get('ajax/getInfosFiche.php?action=UserInformations')
}
});
What i did (data is an object, which means if multiple vues use it and one modify the data, it will be modified for all the vues) :
Vue.component('user-infos', {
data: {
userInfos = axios.get('ajax/getInfosFiche.php?action=UserInformations')
}
});
What you need is a data store, that holds your data and can be used from different components.
You could for example take a look at VueX.
Another way is to define your own custom store, which does not need to be as heavy as VueX.
One way to do this is create and export an observable object and import it from the components that need to have access to the store.
The way I am currently using it is by making the store object available through a global mixin. That way, every component has access to "this.$store" which holds my data.
const state = Vue.observable({ data: {} });
Vue.mixin({
computed: {
$store: {
get: function() {
return state.data;
},
set: function(newData) {
state.data = newData;
}
}
}
});
You can see the full example here.
Example Implementation

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!

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.