vuex: how to save data from multiple components? - vue.js

If I follow the vuex state docu I get a simple counter. Now I can use this counter as a component like this: https://jsfiddle.net/zr86xtqg/. Its easy to note that all counters use the same data (like a component without data: function(){...}).
What do I need to do to get a real component with unique data? Should I add an array to the store and push each counter into it?
Sorry for this simple question but I am totally new to this state managment / data handling.
Maybe it's nice to know why I need it: There is a place in my app with a variable mix of components and another place where I have to display some of the component data. I googled a bit and found many recommendation to use vuex.
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment: state => state.count++,
decrement: state => state.count--
}
})
Vue.component('test-counter', {
computed: {
count () {
return store.state.count
}
},
template: '<div><p>{{ count }}</p><button #click="increment">+</button><button #click="decrement">-</button><p></div>',
methods: {
increment () {
store.commit('increment')
},
decrement () {
store.commit('decrement')
}
}
})
new Vue({
el: '#app'
})
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vuex/3.1.2/vuex.min.js"></script>
<div id="app">
<test-counter></test-counter>
<test-counter></test-counter>
</div>

Yeah, you can make multiple store for various modules. Below I'll show you an example that how I make the store for many modules.
IMPORTANT I recommend you to use namespace=true, It allow you differenciate from each other
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const storeTercero = {
namespaced:true,
state: {
razonSocial: null
},
mutations:{
setRazon(pState, pRazon){
pState.razonSocial = pRazon
}
}
}
const storeFactura = {
namespaced:true,
state:{
numero: null
},
mutations:{
setNumero(pState, pNumero){
pState.numero = pNumero
}
}
}
const storeProducto = {
namespaced: true,
state:{
arr_pestanas: []
},
mutations:{
setPestanas(pState,pArrPestanas){
pState.arr_pestanas = pArrPestanas
},
setFolia1(pState,pObjFolia1){
pState.arr_pestanas.folia1 = pObjFolia1
},
setCombinaciones(pState,pObjCombinaciones){
pState.arr_pestanas.push(pObjCombinaciones)
},
limpiarPestanas(pState){
pState.arr_pestanas = []
},
actualizarFolia(pState,payload){
switch (payload.fila) {
case 1:
pState.arr_pestanas[payload.indice].folia1= {
valor:payload.objeto.value,
texto:payload.objeto.display
}
break;
case 2:
pState.arr_pestanas[payload.indice].folia2= {
valor:payload.objeto.value,
texto:payload.objeto.display
}
break;
case 3:
pState.arr_pestanas[payload.indice].folia3= {
valor:payload.objeto.value,
texto:payload.objeto.display
}
break;
default:
pState.arr_pestanas[payload.indice].folia1= null
pState.arr_pestanas[payload.indice].folia2= null
pState.arr_pestanas[payload.indice].folia3= null
break;
}
},
eliminarIndice(pState,payload){
pState.arr_pestanas.splice(payload.indice,1)
}
},
actions:{
actualizarFolia({commit},payload){
commit('actualizarFolia',payload)
},
cambiarFoliaTodos({state,commit},payload){
state.arr_pestanas.forEach((valor,indice,array) => {
commit('actualizarFolia',{
indice,
fila:payload.fila,
objeto:payload.objeto
})
});
},
eliminarCombinacion({commit},payload){
if (payload.indice > -1) {
commit('eliminarIndice',{
indice:payload.indice
})
}
}
},
getters:{
pestanasCount: state => {
return state.arr_pestanas.length
}
}
}
const storeCartera = {
namespaced: true,
state:{
arrCarteraPagos: []
},
mutations:{
actualizarVlrPago(pState,payload){
pState.arrCarteraPagos[payload.fila] = payload.valorPago;
},
setVlrCarteraPagos(pState,payload){
pState.arrCarteraPagos.filter(function(item){
if(item.id === payload.id){
item.valorPagado = payload.vlr;
}
})
},
setArrCarteraPagos(pState,payload){
pState.arrCarteraPagos = payload
}
}
}
const store = new Vuex.Store({
strict: true,
modules:{
terceros: storeTercero,
facturas: storeFactura,
productos: storeProducto,
cartera: storeCartera
}
})
export default store

Is this what you are trying to achieve?
HTML
<div id="components-demo">
<button-counter></button-counter>
<button-counter></button-counter>
<button-counter></button-counter>
</div>
JS
// Define a new component called button-counter
Vue.component('button-counter', {
data: function () {
return {
count: 0
}
},
template: '<button v-on:click="count++">You clicked me {{ count }} times.</button>'
})
new Vue({ el: '#components-demo' })
DEMO
https://jsfiddle.net/emanuell_minciu/L4xot38s/
SOURCE:
https://v2.vuejs.org/v2/guide/components.html
DEMO UPDATE (Vuex)
https://jsfiddle.net/emanuell_minciu/Ljno612k/3/
DEMO UPDATE (based on new requirements)
https://jsfiddle.net/emanuell_minciu/cr5w87g4/53/

Related

how to get nested getters in vuex nuxt

i have store/index.js like this
new Vuex.Store({
modules: {
nav: {
namespaced: true,
modules: {
message: {
namespaced: true,
state: {
count: 0,
conversations: [],
},
getters: {
getCount: state => {
return state.count;
},
},
mutations: {
updateCount(state) {
state.count++;
},
},
actions: {},
},
requests: {
namespaced: true,
state: {
friends: [],
},
getters: {
getFriends: state => {
return state.friends;
},
},
mutations: {
pushFriends(state, data) {
state.friends.push(data);
},
},
actions: {
pushFriends(commit, data) {
commit('pushFriends', data);
},
},
},
},
},
},
});
i want to use getters in computed property i have tested like this
computed: {
...mapGetters({
count: 'nav/message/getCount',
}),
},
butt getting error
[vuex] unknown getter: nav/message/getCount
what is am missing here
i also want to make separate folder for every modules like my nav have 3 modules message, requests & notifications
i did try but nuxt blow up my codes
I think your index is wrong, the correct thing is to separate the modules independently, something like this:
in your store/index.js
export const state = () => ({
config: {
apiURL: 'https://meuapp.com'
}
})
export const mutations = { }
export const actions = { }
// getters
export const getters = {
test: state => payload => {
if (!payload)
return {
message: 'this is an messagem from index without payload test.', // you don't need pass any payload is only to show you how to do.
result: state.config
}
else
// return value
return {
message: 'this is an message from index test with payload.',
result: state.config, // here is your index state config value
payload: payload // here is yours params that you need to manipulate inside getter
}
}
}
here is your store/navi.js
export const state = () => ({
navi: {
options: ['aaa', 'bbb', 'ccc']
}
})
export const mutations = { }
export const actions = { }
// getters
export const getters = {
test: state => payload => {
if (!payload)
return {
message: 'this is a messagem from nav store without payload test.', // you don't need pass any payload is only to show you how to do.
result: state.navi
}
else
// return value
return {
message: 'this is an messagem from navi test with payload.',
result: state.navi, // here is your index state config value
payload: payload // here is yours params that you need to manipulate inside getter
}
}
}
then in your component you can use as a computed properties:
<template>
<div>
without a paylod from index<br>
<pre v-text="indexTest()" />
with a paylod from index<br>
<pre v-text="indexTest( {name: 'name', other: 'other'})" />
without a paylod from navi<br>
<pre v-text="naviTest()" />
with a paylod from navi<br>
<pre v-text="naviTest( {name: 'name', other: 'other'})" />
access getters from methods<br>
<pre>{{ accessGetters('index') }}</pre>
<pre v-text="accessGetters('navi')" />
<br><br>
</div>
</template>
<script>
import {mapGetters} from 'vuex'
export default {
computed: {
...mapGetters({
indexTest: 'test',
naviTest: 'navi/test'
})
},
methods: {
accessGetters (test) {
if (test && test === 'index' ) {
console.log('test is', test) // eslint-disable-line no-console
return this.indexTest()
}
else if (test && test === 'navi') {
console.log('test is:', test) // eslint-disable-line no-console
return this.naviTest()
}
else {
return 'test is false'
}
}
}
}
</script>
Whenever possible separate your code into smaller parts, one part for each thing. This makes it easier for you to update and keep everything in order.
Hope this helps.
I came here to find a way to access the getters of a module that was nested inside another module in Vue.js and the following solution worked for me:
this.$store.getters['outerModuleName/innerModuleName/nameOfTheGetter']
Maybe this helps someone with a similar problem.

How to commit received data to Vue store?

I'm trying to:
get element's data #click using getDetails method and put it into fileProperties: []
and then send that data to store using fileDetails computed property
This worked for my other components which have v-model and simple true/false state, but I'm not sure how to send the created by the method array of data to the store properly.
In other words, how do I make this computed property to get the data from fileProperties: [] and commit it to store? The fileDetails computed property below is not committing anything.
Code:
[...]
<div #click="getDetails(file)"></div>
[...]
<script>
export default {
name: 'files',
data () {
return {
fileProperties: []
}
},
props: {
file: Object
},
methods: {
getDetails (value) {
this.fileProperties = [{"extension": path.extname(value.path)},
{"size": this.$options.filters.prettySize(value.stat.size)}]
}
},
computed: {
isFile () {
return this.file.stat.isFile()
},
fileDetails: {
get () {
return this.$store.state.Settings.fileDetails
},
set (value) {
this.$store.commit('loadFileDetails', this.fileProperties)
}
}
}
}
</script>
store module:
const state = {
fileDetails: []
}
const mutations = {
loadFileDetails (state, fileDetails) {
state.fileDetails = fileDetails
}
}
Example on codepen:
https://codepen.io/anon/pen/qxjdNo?editors=1011
In this codepen example, how can I send over the dummy data [ { "1": 1 }, { "2": 2 } ] to the store on button click?
You are never setting the value for fileDetails, so the set method of the computed property is never getting called. Here's the documentation on computed setters.
If the fileProperties data is really just the same as the fileDetails data, then get rid of it and set fileDetails directly in your getDetails method.
Here's a working example:
const store = new Vuex.Store({
state: {
fileDetails: null
},
mutations: {
loadFileDetails (state, fileDetails) {
state.fileDetails = fileDetails
}
}
})
new Vue({
el: '#app',
store,
data() {
return {
fileProperties: null
}
},
methods: {
getDetails (value) {
this.fileDetails = [{"1": 1}, {"2": 2}]
}
},
computed: {
fileDetails: {
get () {
return this.$store.state.fileDetails
},
set (value) {
this.$store.commit('loadFileDetails', value)
}
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vuex/3.0.1/vuex.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.13/vue.min.js"></script>
<div id="app">
<h1>element data:</h1>
{{fileDetails}}
<hr>
<h1>store data:</h1>
<p>(should display the same data on button click)</p>
{{fileDetails}}
<hr>
<button #click="getDetails">btn</button>
</div>

Rerender component after state change vue.js

I am working with NuxtJS and VueJS. I'm having a problem with a component not re-rendering after the state changed.
index.js file
Vue.use(Vuex)
const state = {
productsHome: [],
accessToken: {},
collections: {},
product: {},
cart: {},
}
const getters = {
productForHomepage (state) {
return state.productsHome
},
productForPdp (state) {
return state.product
},
cart (state){
return state.cart
}
}
const actions = {
nuxtServerInit (context) {
//good place to set language
},
GET_HOME(){
api.getHomepageProducts().then(response => {
this.commit('setHomeProducts', response.data)
})
},
GET_PDP(sth){
api.findBySlug(this.app.router.history.current.params.slug).then(response => {
this.commit('setPDPData', response.data)
})
},
ADD_TO_CART(store, id){
api.addToCart(id).then(res => {
store.commit('updateCart', res.data)
})
}
}
const mutations = {
setHomeProducts(state, data){
state.productsHome = data
},
setPDPData(state, data){
state.product = data[0]
},
updateCart(state, data){
for (var optbox of data) {
state.cart[optbox.id] = optbox;
}
// state.cart.set('iteams', 'count', 1)
}
}
const createStore = () => {
return new Vuex.Store({
state,
getters,
mutations,
actions
});
}
export default createStore;
and this is the component
<template>
<div>
<div class="content">
<p>
This is cart
</p>
{{ cart }}
</div>
</div>
</template>
<script>
export default {
data() {
return {
cart: this.$store.state.cart
}
},
watch: {
cart: function(val){
cart = this.$store.state.cart
}
},
methods: {
updateCart: function(){
console.log(this)
}
}
}
</script>
When you do this:
data() {
return {
cart: this.$store.state.cart
}
}
You initilise the data with the value of the cart state, but it won't keep changing when the cart state changes, it's a one time deal, as you can see in this JSFiddle
What you actually want to do is use a computed:
computed: {
cart(){
return this.$store.state.cart
}
}
Now whenever cart state changes in your store, so too will the value of cart in your component.
And here's the JSFiddle for that: https://jsfiddle.net/craig_h_411/zrbk5x6q/

Vuex float based store value stored as string

I've created Vuejs frontend project on my desktop. I am using Vuex for the centralized state management. I've TotalAmount and Profit values on my states and I am trying to do addition and subtract operations for both of those states. Sure, decrease working well but increase function concatenate those to states.
Let's suppose:
TotalProfit:1.05465
Profit:0.0012
increase function returns on 1.054650.0012.
Store.js File:
const state = {
TotalBalance: 0,
Profit:0.00000000
};
const actions = {
setTotalBalance(context, data) {
context.commit('setTotalBalance', parseFloat(data).toFixed(8))
},
increaseTotalBalance(context, data) {
context.commit('increaseTotalBalance', parseInt(data).toFixed(8))
},
decreaseTotalBalance(context, data) {
context.commit('decreaseTotalBalance', parseFloat(data).toFixed(8))
},
setProfit(context, data) {
context.commit('setProfit', data)
},
}
const mutations = {
setTotalBalance(state,TotalBalance){
state.TotalBalance = Number(parseFloat(TotalBalance).toFixed(8));
},
increaseTotalBalance(state,Balance){
state.TotalBalance = Number(parseFloat(state.TotalBalance ).toFixed(8))+Number(parseFloat(Balance).toFixed(8))
},
decreaseTotalBalance(state,Balance){
state.TotalBalance = parseFloat(state.TotalBalance ).toFixed(8)-parseFloat(Balance).toFixed(8)
},
setProfit(state,Profit){
state.Profit = Number(parseFloat(Profit).toFixed(8))
}
}
const getters = {
getTotalBalance: state=>{
return state.TotalBalance
},
getProfit: state => {
return state.Profit
}
}
export default new Vuex.Store({
state,
getters,
mutations,
actions
})
As you can see from the file above, I tried many different ways however not found any solution myself.
toFixed returns a string, so you get concatenated strings. Instead you can just do +string to convert a string into number, and cut 8 digits in a getter when you need to display it:
const store = new Vuex.Store({
state: {
TotalBalance: 1.05465,
Profit: 0.0012
},
mutations: {
increaseTotalBalance(state,Balance){
state.TotalBalance = +state.TotalBalance + +Balance;
},
decreaseTotalBalance(state,Balance){
state.TotalBalance = +state.TotalBalance - +Balance;
},
}
})
new Vue({
el: '#app',
store,
computed: {
total: function() {
return Number(this.$store.state.TotalBalance).toFixed(8);
},
profit: function() {
return Number(this.$store.state.Profit).toFixed(8);
}
},
methods: {
inc: function() {
this.$store.commit('increaseTotalBalance', this.profit)
},
dec: function() {
this.$store.commit('decreaseTotalBalance', this.profit)
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.3.0/vue.js"></script>
<script src="https://unpkg.com/vuex#2.3.1"></script>
<div id="app">
<p>{{ total }}, {{ profit }}</p>
<button #click="inc">Inc</button>
<button #click="dec">Dec</button>
</div>

How do I set initial state in Vuex 2?

I am using Vue.js 2.0 and Vuex 2.0 for a small app. I am initializing the store in the 'created' life-cycle hook on the root Vue instance by calling an action that retrieves the initial state from an API....like so in my Root Component:
const app = new Vue({
el: "#app",
router,
store,
data: {
vacation: {},
},
components: {
'vacation-status': VacationStatus,
},
created() {
//initialize store data structure by submitting action.
this.$store.dispatch('getVacation');
},
computed: {},
methods: {}
});
This is working just fine. Here is the action on my store that I'm calling here:
getVacation({ commit }) {
api.getVacation().then(vacation => commit(UPDATE_VACATION, vacation))
}
The mutation that this is committing with 'UPDATE_VACATION' is here:
[UPDATE_VACATION] (state, payload) {
state.vacation = payload.vacation;
},
My Problem: When I load the app, all my components that are 'getting' values from the store throw errors I'm trying to access 'undefined' values on the store. In other words, state hasn't been initialized yet.
For example, I have a component that has getters in Child Components like this:
computed: {
arrival() {
return this.$store.getters.arrival
},
departure() {
return this.$store.getters.departure
},
countdown: function() {
return this.$store.getters.countdown
}
}
All these getters cause errors because 'vacation' is undefined on the state object. It seems like an asynchronous problem to me, but could be wrong. Am I initializing my store state in the wrong spot?
Vue.use(Vuex);
export default new Vuex.Store({
state: {},
getters: {
getVacation: state => {
return state.vacation
},
guests: state => {
return state.vacation.guests
},
verifiedGuests: state => {
return state.vacation.guests.filter(guest => guest.verified)
},
emergencyContacts: state => {
return state.emergency_contacts
},
arrival: state => {
return state.vacation.check_in
},
departure: state => {
return state.vacation.check_out
},
countdown: state => {
let check_in = new Date(state.vacation.check_in);
let now = new Date();
if ((now - check_in) > 0) {
return 'This vacation started on ' + check_in;
}
let difference = check_in - now;
let day = 1000 * 60 * 60 * 24;
return Math.ceil(difference / day) + " days until your vacation";
}
},
mutations: {
[UPDATE_VACATION](state, payload) {
state.vacation = payload.vacation;
},
[ADD_GUEST](state, payload) {
state.vacation.guests.push(payload.guest);
},
[REMOVE_GUEST](state, payload) {
state.vacation.guests.filter(guest => {
debugger;
return guest.id != payload.guest.id
})
},
[UPDATE_GUEST](state, payload) {
state.vacation.guests.map(guest => {
// Refactor Object.assign to deep cloning of object
return guest.id === payload.guest.id ? Object.assign({}, guest, payload.guest) : guest;
})
},
[ADD_EMERGENCY](state, payload) {
state.vacation.emergency_contacts.push(payload.emergency_contact)
},
[REMOVE_EMERGENCY](state, payload) {
state.vacation.emergency_contacts.filter(contact => contact.id !== payload.emergency_contact.id)
},
[UPDATE_EMERGENCY](state, payload) {
state.vacation.emergency_contacts.map(contact => {
// Refactor not needed because emergency_contact is a shallow object.
return contact.id === payload.emergency_contact.id ? Object.assign({}, contact, payload.emergency_contact) : contact;
});
}
},
actions: {
getVacation({
commit
}) {
api.getVacation().then(vacation => commit(UPDATE_VACATION, vacation))
},
addGuest({
commit
}, guest) {
commit(ADD_GUEST, guest);
},
removeGuest({
commit
}, guest) {
commit(REMOVE_GUEST, guest);
},
updateGuest({
commit
}, guest) {
commit(UPDATE_GUEST, guest);
},
addEmergency({
commit
}, guest) {
commit(ADD_EMERGENCY, contact)
},
removeEmergency({
commit
}, contact) {
commit(REMOVE_EMERGENCY, contact)
},
updateEmergency({
commit
}, contact) {
commit(UPDATE_EMERGENCY, contact)
},
updateServer(store, payload) {
return api.saveVacation(payload)
}
}
});
Just so the solution is clear to others:
I wasn't setting my initial state quite properly in the store itself. I was pulling in the data, and updating the store correctly, but the store needed to be initialized like this:
export default new Vuex.Store({
state: {
vacation: {} //I added this, and then justed updated this object on create of the root Vue Instance
},
});
I think you're doing everything right. Maybe you're just not creating the getters correctly (can't see any definition in your code). Or your setting the initial state not correctly (also not visible in your snippet).
I would use mapState to have the state properties available in components.
In the demo simply add users to the array in mapState method parameter and the users data will be available at the component. (I've just added the getter users to show how this is working. That's not needed if you're using mapState.)
Please have a look at the demo below or this fiddle.
const api =
'https://jsonplaceholder.typicode.com/users'
const UPDATE_USERS = 'UPDATE_USERS'
const SET_LOADING = 'SET_LOADING'
const store = new Vuex.Store({
state: {
users: {},
loading: false
},
mutations: {
[UPDATE_USERS](state, users) {
console.log('mutate users', users)
state.users = users;
console.log(state)
}, [SET_LOADING](state, loading) {
state.loading = loading;
}
},
getters: {
users(state) {
return state.users
}
},
actions: {
getUsers({commit}) {
commit(SET_LOADING, true);
return fetchJsonp(api)
.then((users) => users.json())
.then((usersParsed) => {
commit(UPDATE_USERS, usersParsed)
commit(SET_LOADING, false)
})
}
}
})
const mapState = Vuex.mapState;
const Users = {
template: '<div><ul><li v-for="user in users">{{user.name}}</li></ul></div>',
computed: mapState(['users'])
}
new Vue({
el: '#app',
store: store,
computed: {
...mapState(['loading']),
//...mapState(['users']),
/*users () { // same as mapState
return this.$store.state.users;
}*/
users() { // also possible with mapGetters(['users'])
return this.$store.getters.users
}
},
created() {
this.$store.dispatch('getUsers')
},
components: {
Users
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/fetch-jsonp/1.0.5/fetch-jsonp.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.1.10/vue.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vuex/2.1.1/vuex.min.js"></script>
<div id="app">
<div v-if="loading">loading...</div>
<users></users>
<pre v-if="!loading">{{users}}</pre>
</div>
You can create a function that returns the initial state, and use it into your Vuex instance, like this:
function initialStateFromLocalStorage() {
...
const empty = {
status: '',
token: '',
user: null
}
return empty;
}
export default new Vuex.Store({
state: initialStateFromLocalStorage,
...
As soon as you return an object for the state, you can do whatever you want inside that function, right?