Vue push object into array and make immutable - javascript

I'm pretty new to Vue/Javascript so this is probably a super simple answer. I have a component that opens a modal where you can choose a product to add to order. The problem is that I when I modify the price then re-open the modal that all the prices are reset.
How can the pickProduct function be modified so that the changes persist when re-opening the modal?
Codepen
new Vue({
el: '#app',
data: () => ({
orderForm: {
lines: []
},
defaultLine: {
product: null,
price: ''
}
}),
methods: {
pickProduct(product) {
const emptyLine = Object.assign({}, this.defaultLine)
const newProduct = Object.assign({}, product)
Object.assign(emptyLine, newProduct)
newProduct.product = newProduct.id
// this.orderForm.lines.push(newProduct)
this.orderForm.lines = [...this.orderForm.lines, newProduct]
}
}
})

You are using :value in your price input, that doesn't actually change the value for the object you are editing. So this has nothing to do immutability. To actually bind the value for the object, use v-model instead of value.
So change this:
<input type="text" :value="item.price" />
to
<input type="text" v-model="item.price" />
CODEPEN

Related

How update nested object array value directly by v-model in VUE?

I need to update my value which is in JSON thru v-model
{ class: "data.child",
"myform.input1": [true, "<input1 value>"]
}
<input type="text" v-model="<what to put here?>" > //so that directly value can be update in my vue data property JSON mentioned above
Cant do it directly with v-model, unless you want to change your input type to maybe multi select.
If you really want the exact output, can listen onchange event like below.
Or can just use v-model and enter your data as you want...but will need to convert to array.
const jsonData = { class: "data.child",
"myform.input1": [true, "<input1 value>"],
"myform.input2": [true, "<input1 value>"]
}
const App = {
template: `<div>
<input type="text" v-model="data['myform.input2']"/>
<input type="text" #change="update"/>
<p>{{JSON.stringify(data, null, 2)}}</p>
</div>`,
methods: {
update: function(event) {
this.data['myform.input1'] = [true, event.target.value];
}
}
,
data(){
return {data: jsonData}
}
}
new Vue({
render: h => h(App),
}).$mount("#app");
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
</div>

How in vuex.js to save in the "store" the text entered in the input? Where did i make a mistake?

I suffer the third day. Help me please. How in vuex.js save the text entered into the input in the "store", and then add it to the Value of the same input itself.
I'm trying to do it like this but somewhere I make a mistake.
HTML
<f7-list-input
label="Username"
name="username"
placeholder="Username"
type="text"
:value="newUserName"
#input="username = $event.target.value"
required validate
pattern="[3-9a-zA-Zа-яА-ЯёЁ]+"
v-model="saveUserName"
/>
SCRIPT
export default {
data() {
return {
username: '',
password: '',
};
},
methods: {
signIn() {
const self = this;
const app = self.$f7;
const router = self.$f7router;
router.back();
app.dialog.alert(`Username: ${self.username}<br>Password: ${self.password}`, () => {
router.back();
});
},
saveUserName(){
this.$store.commit(saveName);
}
},
computed:{
userName(){
return this.$store.state.newUserName;
}
}
};
STORE
export default new Vuex.Store({
state:{
userNameStor: 'newUserName',
userPasswordStor:''
},
mutations:{
saveName(state){
userNameStor:newUserName;
return newUserName;
}
}
});
Let's explain the whole functionnality, and then some code.
The input is in the template part of a component.
The component contain also a script part, which trigger code based on template events and so on.
The component code can trigger mutations (for state change), which are the way you store something in the store.
you have this screen to store flow:
1/ component template event => 2/ component script code => 3/ execute mutation on store
For the other side, you have this:
state => mapMutation in component computed property => component template.
Inside component script you can map a store value into a computed property of the component (with mapMutations helper). Then you map this field in your template from the component computed property.
1 - Your template
#input is the event occuring when input change by user action.
:value is the value of the input, defined programmatically.
v-model is a shorcut for using #input and :value at the same time. Don't use it with :value and #input.
Ok minimal Template:
<f7-list-input
type="text"
:value="username"
#input="changeUsername"
/>
Inside the script, you just have to link the changeUsername method to the mutation (with mapMutation), and also define a computed property whose name is username and that is a map of username from the store (with mapState).
import {mapState, mapMutations} from "vuex"
export default {
methods:{
...mapMutations({
changeUsername:"saveName"
},
computed:{
...mapState({
username:state=>state.username
}),
}
};
Consider looking at Vue doc about this mutations and state

Vue JS form issue - mutating vuex store state outside handlers

I am currently getting the [vuex] Do not mutate vuex store state outside mutation handlers error only when I try to edit the form, if I am posting a new plugin it works fine. From this doc I'm not sure where I'm going wrong - while I fetch the plugin from vuex, I try to give the local state those values and then leave vuex alone. Ideally once fetched vuex, I wouldn't need to touch it again until the form is submitted. But I'm not sure what is causing the error exactly
<template>
<div>
<h4>{{this.$route.query.mode==="new"?"New":"Edit"}} Plugin</h4>
<form class="">
<label>Id</label>
<input :value="plugin.id" class="" type="text" #input="updateId">
<label>Name</label>
<input :value="plugin.name" class="" type="text" #input="updateName">
<label>Description</label>
<textarea :value="plugin.description" class="" type="text" #input="updateDescription"></textarea>
<label>Version</label>
<input :value="plugin.version" class="" type="text" #input="updateVersion">
<button type="submit" #click.prevent="submitForm">Submit</button>
</form>
</div>
</template>
<script>
import util from '~/assets/js/util'
export default {
created() {
if (this.mode === 'edit') {
this.plugin = this.$store.state.currentLicence.associatedPlugins.find(p => p.pluginId === this.$route.query.pluginId)
}
},
methods: {
updateId(v) {
this.plugin.id = v.target.value
},
updateName(v) {
this.plugin.name = v.target.value
},
updateDescription(v) {
this.plugin.description = v.target.value
},
updateVersion(v) {
this.plugin.version = v.target.value
}
},
computed: {
mode() { return this.$route.query.mode }
},
data: () => ({
plugin: {
id: null,
name: null,
description: null,
version: null
}
})
}
</script>
Thanks for any help, clearly my understanding of the way that vuex and local state are handled is flawed
You are getting this error because you are editing the state directly.
this.plugin = this.$store.state.currentLicence.associatedPlugins.find(p => p.pluginId === this.$route.query.pluginId) - this is exactly this part of code where you put the object from the store directly into the data, therefore by editing the field you are directly editing the state. Don't do that!
You should always use stuff like (I am not sure how nested computed will work but I don't think you have to nest it):
computed: {
plugin: {
id: {
get () { // get it from store }
set (value) { // dispatch the mutation with the new data }
}
}
}
There is a nice package whill will do most work for you: https://github.com/maoberlehner/vuex-map-fields . You can use it to semi-automatic generate computed with getters and setters for each field.

Updating an item in an array updates them all

I'm working on an application using vuejs with vuex which uses projects, with each project having one or more jobs.
I can add, delete and update the jobs. The adding and deleting is working perfect, but the updating is not.
The state in the vuex dev tools:
My HTML:
<div class="job-compact row" v-for="(job, index) in project.jobs">
<div class="col-md-6">
<div class="form-group" :class="{'has-error' : errors.has('jobs.' + index + '.function')}">
<input type="text" name="jobs[function][]" class="form-control" v-model="job.function" #change="updateJobValue(index, 'function', $event.target.value)"/>
</div>
</div>
<div class="col-md-4">
<div class="form-group" :class="{'has-error' : errors.has('jobs.' + index + '.profiles')}">
<input type="number" name="jobs[profiles][]" class="form-control" v-model="job.profiles" #change="updateJobValue(index, 'profiles', $event.target.value)"/>
</div>
</div>
<div class="col-md-2">
<button v-if="index == 0" class="btn btn-success btn-sm" #click="addJob"><i class="fa fa-plus"></i></button>
<button v-if="index > 0" class="btn btn-danger btn-sm" #click="deleteJob(index);"><i class="fa fa-minus"></i></button>
</div>
</div>
As you can see, I have a v-for that is showing all my jobs. When editing a value inside my jobs, I use the #change event to update my value. And, at the bottom, I have two buttons to add and remove a job row.
My stores are divided into modules. The main store looks like this:
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex);
const state = {};
const getters = {};
const mutations = {};
const actions = {};
//Separate Module States
import jobCreator from './modules/job-creator/store';
export default new Vuex.Store({
modules: {
jobCreator: jobCreator
},
state,
actions,
mutations,
getters
});
The module store for this specific problem:
import store from './../../store'
const state = {
project: {
title: null,
description: null,
jobs: []
},
defaultJob: {
function: '',
title: '',
description: '',
profiles: 1,
location_id: '',
category_id: '',
budget: '',
},
};
const getters = {}
const mutations = {
addJob(state, job) {
state.project.jobs.push(job);
},
deleteJob(state, index) {
state.project.jobs.splice(index, 1);
},
updateJobValue(state, params) {
Object.assign(state.project.jobs[params.jobIndex], {
[params.field]: params.value
});
}
};
const actions = {
addJob: function (context) {
context.commit('addJob', state.defaultJob);
},
deleteJob: function (context, index) {
context.commit('deleteJob', index);
},
updateJobValue: function (context, params) {
context.commit('updateJobValue', params);
},
};
const module = {
state,
getters,
mutations,
actions
};
export default module;
The project state is mapped to a computed property of my vue instance:
computed: {
...mapState({
project: state => state.jobCreator.project,
}),
}
The problem is the following: In the image of the application, you can see that I entered "vin" in one of the fields, but all of the fields are updating.
So, all of the function fields of all the jobs have been updated to my last entry, instead of only the one I want.
What am I doing wrong?
PS:
I also tried the following in my mutation function:
updateJobValue(state, params) {
var job = state.project.jobs[params.jobIndex];
job[params.field] = params.value;
Vue.set(state.project.jobs, params.jobIndex, job);
}
But it's giving me the same result.
UPDATE: As requested, I created a jsFiddle to show my problem
The issue is in your addJob action:
addJob: function (context) {
context.commit('addJob', state.defaultJob);
},
You are referencing the state.defaultJob object each time you add a new job. That means each item in the state.project.jobs array is referencing the same object.
You should create a copy of the object when passing it to the addJob mutation:
addJob: function (context) {
context.commit('addJob', Object.assign({}, state.defaultJob));
},
Or, just pass in a new object with the default properties each time:
addJob: function (context) {
context.commit('addJob', {
function: '',
title: '',
description: '',
profiles: 1,
location_id: '',
category_id: '',
budget: '',
});
},
Here's a working fiddle.
Here's a post explaining how variables are passed in Javascript: Javascript by reference vs. by value
I would give the following advice:
Use v-bind:value="job.function instead of v-model="job.function" because you want only a one way binding. This your code more predictable.
Add a v-key="job" to your v-for="(job, index) in project.jobs" element just to be sure that the rendering works correctly.
The first two lines should be enought, the object is still reactive.
var job = state.project.jobs[params.jobIndex];
job[params.field] = params.value;
Vue.set(state.project.jobs, params.jobIndex, job);
PS: In my fiddle the #change did only fire when i hit enter or left the input.

Vue.js dynamic add and remove

I'm learning Vue.js for my game and I was wondering if there is a way to dynamically add and remove components in Vue.js ?
Here's my current code
var vue = new Vue({
el: "#fui",
template: ``
})
const HelloCtor = Vue.extend({
props: ['text'],
template: '<div class="hello">{{ text }}</div>',
});
const vm = new HelloCtor({
data: {
text: 'HI :)'
}
});
/*
How can I do something like this?
vue.add(vm);
vue.remove(vm);
*/
The code basically speaks for himself
So, is it possible (and how?) to dynamically add and remove Vue.js components to a Vue?
You need a place to put vm in the template. Then you can $mount the component manually to an element with vm.$mount('el'). You can also delete the element with vm.$destroy(true). Destroy won't delete the element from the DOM. You'll need to do that manually with something like (vm.$el).remove()
I'm not 100% this is what you're looking for, and when you find yourself manually calling $destroy() you are probably not doing things right…but it does let you take control of the creating and destruction of components.
Something like this will let you create then destroy your component (note in this case once you destroy vm it's gone):
<div id="fui">
<button #click="make">Make</button>
<button #click="bye">destroy</button>
<div id="mountme"></div>
</div>
<script>
const HelloCtor = Vue.extend({
props: ['text'],
template: '<div class="hello">This has been {{ text }}</div>',
})
const vm = new HelloCtor ({
data: {
text: "Mounted"
}
})
var vue = new Vue({
el: "#fui",
template: ``,
methods: {
make: () => {
vm.$mount('#mountme')
},
bye: () => {
vm.$destroy(true);
(vm.$el).remove();}
}
})
</script>

Categories