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>
Related
I want to create a component based on ajax api response or data which include:
template
data
methods - there may be several methods
Remark: response or data is dynamic and it is not saved in file.
I have tried to generate and return result like :
<script>
Vue.component('test-component14', {
template: '<div><input type="button" v-on:click="changeName" value="Click me 14" /><h1>{{msg}}</h1></div>',
data: function () {
return {
msg: "Test Componet 14 "
}
},
methods: {
changeName: function () {
this.msg = "mouse clicked 14";
},
}
});
</script>
and do compile above code :
axios.get("/api/GetResult")
.then(response => {
comp1 = response.data;
const compiled = Vue.compile(comp1);
Vue.component('result-component', compiled);
})
.catch(error => console.log(error))
I got error on Vue.compile(comp1) -
Templates should only be responsible for mapping the state to the UI. Avoid placing tags with side-effects in your templates, such as
<script>, as they will not be parsed.
Thanks in advance
Your Api should return a JSON with every property required by a Vue component (name, data, template, methods), note that methods needs to be converted into an actual js function (check docs about that)
Vue.config.productionTip = false;
Vue.config.devtools = false;
new Vue({
el: '#app',
data() {
return {
apiComponent: { template: '<div>Loading!</div>' }
};
},
methods: {
loadApiComponent() {
setTimeout(() => {
this.buildApiComponent(JSON.parse('{"name":"test-component14","template":"<div><input type=\\\"button\\\" v-on:click=\\\"changeName\\\" value=\\\"Click me 14\\\" /><h1>{{msg}}</h1></div>","data":{"msg":"Test Componet 14 "},"methods":[{"name":"changeName","body":"{this.msg = \\\"mouse clicked 14\\\";}"}]}'));
}, 2000);
},
buildApiComponent(compObject) {
const {
name,
template,
data,
methods
} = compObject;
const compiledTemplate = Vue.compile(template);
this.apiComponent = {
...compiledTemplate,
name,
data() {
return { ...data
}
},
methods: methods.reduce((c, n) => {
c[n.name] = new Function(n.body);
return c;
}, {})
};
}
},
mounted() {
this.loadApiComponent();
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<component :is="apiComponent" />
</div>
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/
I want to return a value from mutation to action.
I this case, I want the last inserted object :
In my mutation, work fine :
mutations: {
insert(state, item) {
const guid = Math.floor(Math.random() * 6) + 1; // any sense, just example
item.guid = guid;
state.data.push(item);
return guid;
},
},
In my action, work fine for the call, not for the return :
actions: {
insert ({ commit }, data) {
return new Promise((resolve) => {
const guid = commit('insert', event);
resolve(guid); // resolve undefined
});
},
},
There is a way to return the guid ?
I need it to emit after with my component...
Thanks
Mutations (commits) don't return values.
And, as mentioned in comments, the best practice is to leave such GUID generation computation to an action and just really commit the state in the mutation.
That being said, you cand send a callback to the mutation and call it. Just make sure the callback code is simple and synchronous (if not, see below).
const store = new Vuex.Store({
strict: true,
state: {
data: []
},
mutations: {
insert(state, {item, callback}) {
const guid = Math.floor(Math.random() * 600) + 1; // any sense, just example
item.guid = guid;
state.data.push(item);
callback(guid);
},
},
actions: {
insert ({ commit }, data) {
return new Promise((resolve) => {
commit('insert', {item: data, callback: resolve});
});
},
},
});
new Vue({
store,
el: '#app',
data: { insertedGuid: 'click button below' },
methods: {
go: async function() {
const guid = await this.$store.dispatch('insert', {name: "Alice"});
this.insertedGuid = guid;
}
},
computed: {
datadata: function() {
return this.$store.state.data
}
},
})
<script src="https://unpkg.com/vue"></script>
<script src="https://unpkg.com/vuex"></script>
<div id="app">
<p>store's data: {{ datadata }}</p>
<p>insertedGuid: {{ insertedGuid }}</p>
<button #click="go">Click to Insert</button>
</div>
If you have no idea of what the callback could be, I suggest you wrap it as
setTimeout(() => callback(guid));
Which would end the mutation right away and send the callback execution later down the queue of the event loop.
you can access the state data by passing it into the action insert ({ commit, state }, data) { ...
example:
actions: {
insert ({ commit, state }, data) {
return new Promise((resolve) => {
commit('insert', event);
const guid = state.data[state.data.length].guid
resolve(guid); // resolve undefined
});
},
},
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>
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?