Unlink state form store - vue.js

I've had a problem
data() {
return {
list: [],
initState: { id: null, data: null }
}
},
computed: {
...mapState({
contacts: (state) => state.ContactBook.Contacts
})
},},
methods: {
setinitState(payload) {
this.initState.data = contacts[this.contactID]
},
How Can I make this.initState static? I need this.initState didnt changes when contacts[this.contactID] changes...

You may use the spread operator like this,
this.initState.data = {...contacts[this.contanctID]}
Spread Operator MDN

state.snapshot = JSON.parse(JSON.stringify(state.Contacts[payload]))

Related

Dynamically changing state vue

For my application I am using Vue-router and store to work together. In my state I am storing a few questions. The content of the questions is saved in my Store like so:
export default new Vuex.Store({
state: {
blabla: [
{
question: 'question1',
answer: false,
givenAnswer: null,
disabled: false,
},
{
question: 'question2',
answer: false,
givenAnswer: null,
disabled: false
}
],
score: null
},
getters: {
getQuestionList: state => {
return state.questions;
}
})
And of course with othe config, but this all works fine. I am getting this data through a Getter in my Vue template by using this code in my Component:
export default {
data() {
return {
questionId: this.$route.params.id - 1
}
},
computed: {
getQuestions () {
return this.$store.getters.getQuestionList
}
}
}
<p>{{ getQuestions[questionId].question }}</p>
Now I want to dynamically mutate the state of every question to disabled:false whenever someone answers the question in order to enable answering the next question. However I can not figure out how to exactly do it.
Can someone put me in the right direction? Thanks!
Edit for clarification: There is a list of 10 questions and when you start, only the first one is enabled at the start. When you answer question 1, question 2 should be enabled (question one also stays enabled), when you answer question 2, question 3 should be enabled, etc.
You can just commit a mutation that sets disabled: false for the specified element:
Cmponent
<button #click="submitQuestion(question.id)"></button>
methods: {
submitQuestion (id) {
// ...
this.$store.commit('ENABLE_QUESTION', id + 1) // passing id of the next question
}
}
Store
state: {
questions: [
{
id: 0,
question: 'question1',
answer: false,
givenAnswer: null,
disabled: false,
},
{
id: 1,
question: 'question2',
answer: false,
givenAnswer: null,
disabled: false
}
]
}
...
mutations: {
ENABLE_QUESTION (state, id) {
let questionIndex = state.questions.findIndex(item => item.id === id)
state.questions[questionIndex].disabled = false
}
}
in your store put this mutation code :
mutations: {
enable (state) {
state.questions.forEach(item => {
item.disabled = false
)}
}
}
and in your component you can commit this mutation easily as follows:
this.$store.commit('enable')

Set data field from getter and add extra computed field

I wanted to set fields inside data using getters:
export default {
data () {
return {
medications: [],
}
},
computed: {
...mapGetters([
'allMedications',
'getResidentsById',
]),
I wanted to set medications = allMedications, I know that we can user {{allMedications}} but my problem is suppose I have :
medications {
name: '',
resident: '', this contains id
.......
}
Now I wanted to call getResidentsById and set an extra field on medications as :
medications {
name: '',
resident: '', this contains id
residentName:'' add an extra computed field
.......
}
I have done this way :
watch: {
allMedications() {
// this.medications = this.allMedications
const medicationArray = this.allMedications
this.medications = medicationArray.map(medication =>
({
...medication,
residentName: this.getResidentName(medication.resident)
})
);
},
},
method: {
getResidentName(id) {
const resident = this.getResidentsById(id)
return resident && resident.fullName
},
}
But this seems problem because only when there is change in the allMedications then method on watch gets active and residentName is set.
In situations like this you'll want the watcher to be run as soon as the component is created. You could move the logic within a method, and then call it from both the watcher and the created hook, but there is a simpler way.
You can use the long-hand version of the watcher in order to pass the immediate: true option. That will make it run instantly as soon as the computed property is resolved.
watch: {
allMedications: {
handler: function (val) {
this.medications = val.map(medication => ({
...medication,
residentName: this.getResidentName(medication.resident)
});
},
immediate: true
}
}

Getting documents with ID from firstore collection

While using Firestore, vuefire, vue-tables-2, I stuck getting document's id.
My data structure is as below.
Here is my code.
<v-client-table :columns="columns" :data="devices" :options="options" :theme="theme" id="dataTable">
import { ClientTable, Event } from 'vue-tables-2'
import { firebase, db } from '../../firebase-configured'
export default {
name: 'Devices',
components: {
ClientTable,
Event
},
data: function() {
return {
devices: [],
columns: ['model', 'id', 'scanTime', 'isStolen'],
options: {
headings: {
model: 'Model',
id: 'Serial No',
scanTime: 'Scan Time',
isStolen: 'Stolen YN'
},
templates: {
id: function(h, row, index) {
return index + ':' + row.id // <<- row.id is undefined
},
isStolen: (h, row, index) => {
return row.isStolen ? 'Y': ''
}
},
pagination: {
chunk: 5,
edge: false,
nav: 'scroll'
}
},
useVuex: false,
theme: 'bootstrap4',
template: 'default'
}
},
firestore: {
devices: db.collection('devices')
},
};
My expectation is devices should id property as vuefire docs.
But array this.devices didn't have id field even if I check it exist it console.
Basically, every document already has id attribute, but it's non-enumerable
Any document bound by Vuexfire will retain it's id in the database as
a non-enumerable, read-only property. This makes it easier to write
changes and allows you to only copy the data using the spread operator
or Object.assign.
You can access id directly using device.id. But when passing to vue-tables-2、devices is copied and lost id non-enumerable attribute.
I think you can workaround using computed property
computed: {
devicesWithId() {
if (!this.devices) {
return []
}
return this.devices.map(device => {
...device,
id: device.id
})
}
}
Then, please try using devicesWithId in vue-tables-2 instead.

watching one value, computing the other one, how to update computed value when watched changes

I am watching one value and I computed another value.
Something like this:
computed: {
modeText: function () {
if(this.mode == 'create')
return 'Create'
else
return 'Edit'
}
},
watch: {
mode(val, old) {
if(val == 'create')
update modeText
else
update modeText
},
},
How do I recompute the computed value when watched value updates?
It seems like modeText is only dependent on mode, so instead of using computed, you can go for something simpler:
data: {
modeText: '',
mode: ''
},
watch: {
mode(val, old) {
this.modeText = this.updateModeText(val);
},
},
methods: {
updateModeText(val) {
if (val === 'create') {
return 'Create';
}
return 'Edit';
}
}

vue.js $watch array of objects

mounted: function() {
this.$watch('things', function(){console.log('a thing changed')}, true);
}
things is an array of objects [{foo:1}, {foo:2}]
$watch detects when an object is added or removed, but not when values on an object are changed. How can I do that?
You should pass an object instead of boolean as options, so:
mounted: function () {
this.$watch('things', function () {
console.log('a thing changed')
}, {deep:true})
}
Or you could set the watcher into the vue instance like this:
new Vue({
...
watch: {
things: {
handler: function (val, oldVal) {
console.log('a thing changed')
},
deep: true
}
},
...
})
[demo]
There is a more simple way to watch an Array's items without having deep-watch: using computed values
{
el: "#app",
data () {
return {
list: [{a: 0}],
calls: 0,
changes: 0,
}
},
computed: {
copy () { return this.list.slice() },
},
watch: {
copy (a, b) {
this.calls ++
if (a.length !== b.length) return this.onChange()
for (let i=0; i<a.length; i++) {
if (a[i] !== b[i]) return this.onChange()
}
}
},
methods: {
onChange () {
console.log('change')
this.changes ++
},
addItem () { this.list.push({a: 0}) },
incrItem (i) { this.list[i].a ++ },
removeItem(i) { this.list.splice(i, 1) }
}
}
https://jsfiddle.net/aurelienlt89/x2kca57e/15/
The idea is to build a computed value copy that has exactly what we want to check. Computed values are magic and only put watchers on the properties that were actually read (here, the items of list read in list.slice()). The checks in the copy watcher are actually almost useless (except weird corner cases maybe) because computed values are already extremely precise.
If someone needs to get an item that was changed inside the array, please, check it:
JSFiddle Example
The post example code:
new Vue({
...
watch: {
things: {
handler: function (val, oldVal) {
var vm = this;
val.filter( function( p, idx ) {
return Object.keys(p).some( function( prop ) {
var diff = p[prop] !== vm.clonethings[idx][prop];
if(diff) {
p.changed = true;
}
})
});
},
deep: true
}
},
...
})
You can watch each element in an array or dictionary for change independently with $watch('arr.0', () => {}) or $watch('dict.keyName', () => {})
from https://v2.vuejs.org/v2/api/#vm-watch:
Note: when mutating (rather than replacing) an Object or an Array, the
old value will be the same as new value because they reference the
same Object/Array. Vue doesn’t keep a copy of the pre-mutate value.
However, you can iterate the dict/array and $watch each item independently. ie. $watch('foo.bar') - this watches changes in the property 'bar' of the object 'foo'.
In this example, we watch all items in arr_of_numbers, also 'foo' properties of all items in arr_of_objects:
mounted() {
this.arr_of_numbers.forEach( (index, val) => {
this.$watch(['arr_of_numbers', index].join('.'), (newVal, oldVal) => {
console.info("arr_of_numbers", newVal, oldVal);
});
});
for (let index in this.arr_of_objects) {
this.$watch(['arr_of_objects', index, 'foo'].join('.'), (newVal, oldVal) => {
console.info("arr_of_objects", this.arr_of_objects[index], newVal, oldVal);
});
}
},
data() {
return {
arr_of_numbers: [0, 1, 2, 3],
arr_of_objects: [{foo: 'foo'}, {foo:'bar'}]
}
}
If your intention is to render and array and watch for changes on rendered items, you can do this:
Create new Component:
const template = `<div hidden></div>`
export default {
template,
props: ['onChangeOf'],
emits: ['do'],
watch: {
onChangeOf: {
handler(changedItem) {
console.log('works')
this.$emit('do', changedItem)
},
deep: true
}
},
}
Register that component:
Vue.component('watcher', watcher)
Use it inside of your foreach rendering:
<tr v-for="food in $store.foods" :key="food.id">
<watcher :onChangeOf="food" #do="(change) => food.name = 'It works!!!'"></watcher>
</tr>