How can I refactor repetitive conditional Vue.js code? - javascript

I have this code in my Vue.js component:
mounted() {
if (localStorage.dobDate) {
this.form.dobDate = localStorage.dobDate;
}
if (localStorage.dobMonth) {
this.form.dobMonth = localStorage.dobMonth;
}
if (localStorage.dobYear) {
this.form.dobYear = localStorage.dobYear;
}
},
watch: {
"form.dobDate": {
handler: function(after, before) {
localStorage.dobDate = after;
},
deep: true
},
"form.dobMonth": {
handler: function(after, before) {
localStorage.dobMonth = after;
},
deep: true
},
"form.dobYear": {
handler: function(after, before) {
localStorage.dobYear = after;
},
deep: true
}
Ask you can see it can get very repetitive, if for example I had a large form, and I don't want to do this for every field. Is there a way I can approach this to make it more DRY? Is there a way I can make it more dynamic for any field in a form for example?

In the mounted hook create an array of localStorage fields ["dobDate","dobMonth","dobYear"] and loop through it using forEach method, for each field localStorage[fieldName] check if it's defined using conditional operator, if it's defined assign it to the correspondant field name in the form data property else pass to the next element:
mounted(){
["dobDate","dobMonth","dobYear"].forEach(field=>{
localStorage[field]?this.form[field]=localStorage[field]:{};
})
}
In the watch property watch the form object deeply (watch its nested fields) then loop through its keys by doing the reciprocal operation made in mounted hook :
watch: {
form: {
handler: function(after, before) {
Object.keys(after).forEach(key=>{
localStorage[key]=after[key]
})
},
deep: true
}
}

Here is another approach with multiple (no deep) watchers.
data: {
form: {},
dateFields: ['dobDate', 'dobMonth', 'dobYear']
},
mounted() {
for (const dateField of this.dateFields) {
if (localStorage[dateField])
this.$set(this.form, dateField, localStorage[dateField])
}
},
created() {
for (const dateField of this.dateFields) {
this.$watch('form.' + dateField, function(after, before) {
localStorage[dateField] = after;
});
}
}
I ignore if it's more or less efficient than only one deep watcher. It may depends on the way your data change.

I'm sure you must have reasons for using localStorage for saving form data in localStorage, so with this code, you can pass the whole form object to localStorage and can retrieve that. in this case, any change in form would make this watch run
mounted() {
if (localStorage.form) {
this.form = localStorage.form
}
},
watch: {
"form": {
handler: function(after, before) {
localStorage.form = after;
},
deep: true
}
}

Related

How to watch dynamic data fields in Vue.js

so i am trying to figure out whether this is possible, i am using a prop to dynamically name a field in data. So essentially i want to add a watcher to the dynamically added data field. Is this possible?
export default {
props: {
type: {
type: String,
required: true
}
},
data() {
return {
[this.type]: {
place_type: "house"
}
}
},
watch: {
"[this.type].place_type": function(val) {
console.log(val);
}
}
}
Here is my attempt, but it does not work with that syntax. Any ideas? Thanks

How to detect changes made in the edited field in Vue?

I have an object like this:
users: {
email: '',
password: ''
}
When user edits something, I want to detect the changes made in the key of the users object & save that key in another object(say editedFields)! How can I do this?
I tried using deep watchers like this:
watch: {
users: {
handler: function(val, oldVal) {
//
},
deep: true
}
}
But I am not sure how to get that edited key! Please help
I think this works:
watch: {
'users.email': function(newEmail, oldEmail) {
//
}
}
I think something like this might work?
data: function() {
return {
user: {
email: '',
password: ''
},
editedFields: []
}
},
watch: {
users: {
handler: function(val, oldVal) {
for (var key in val) {
if(val[key] != oldVal[key]) {
this.data.editedFields.push(key);
}
}
},
deep: true
}
}
But actually, there better way of handling this would be to create a new Vue component for each field. You can then test to see if its been updated in each component.

How to fire an event on Vue switch change

I have a Vue component that has a vue-switch element. When the component is loaded, the switch has to be set to ON or OFF depending on the data. This is currently happening within the 'mounted()' method. Then, when the switch is toggled, it needs to make an API call that will tell the database the new state. This is currently happening in the 'watch' method.
The problem is that because I am 'watching' the switch, the API call runs when the data gets set on mount. So if it's set to ON and you navigate to the component, the mounted() method sets the switch to ON but it ALSO calls the toggle API method which turns it off. Therefore the view says it's on but the data says it's off.
I have tried to change the API event so that it happens on a click method, but this doesn't work as it doesn't recognize a click and the function never runs.
How do I make it so that the API call is only made when the switch is clicked?
HTML
<switcher size="lg" color="green" open-name="ON" close-name="OFF" v-model="toggle"></switcher>
VUE
data: function() {
return {
toggle: false,
noAvailalableMonitoring: false
}
},
computed: {
report() { return this.$store.getters.currentReport },
isBeingMonitored() { return this.$store.getters.isBeingMonitored },
availableMonitoring() { return this.$store.getters.checkAvailableMonitoring }
},
mounted() {
this.toggle = this.isBeingMonitored;
},
watch: {
toggle: function() {
if(this.availableMonitoring) {
let dto = {
reportToken: this.report.reportToken,
version: this.report.version
}
this.$store.dispatch('TOGGLE_MONITORING', dto).then(response => {
}, error => {
console.log("Failed.")
})
} else {
this.toggle = false;
this.noAvailalableMonitoring = true;
}
}
}
I would recommend using a 2-way computed property for your model (Vue 2).
Attempted to update code here, but obvs not tested without your Vuex setup.
For reference, please see Two-Way Computed Property
data: function(){
return {
noAvailableMonitoring: false
}
},
computed: {
report() { return this.$store.getters.currentReport },
isBeingMonitored() { return this.$store.getters.isBeingMonitored },
availableMonitoring() { return this.$store.getters.checkAvailableMonitoring },
toggle: {
get() {
return this.$store.getters.getToggle;
},
set() {
if(this.availableMonitoring) {
let dto = {
reportToken: this.report.reportToken,
version: this.report.version
}
this.$store.dispatch('TOGGLE_MONITORING', dto).then(response => {
}, error => {
console.log("Failed.")
});
} else {
this.$store.commit('setToggle', false);
this.noAvailableMonitoring = true;
}
}
}
}
Instead of having a watch, create a new computed named clickToggle. Its get function returns toggle, its set function does what you're doing in your watch (as well as, ultimately, setting toggle). Your mounted can adjust toggle with impunity. Only changes to clickToggle will do the other stuff.

can't access data variables in watch handler vuejs

I'm trying to set a data variable in a watch handler function for an input field in a VueJs Component. I have something like this:
data() {
return {
params: {
// default params to 1 month
from: new Date().setMonth(new Date().getMonth() - 1),
to: Date.now(),
skip: 0,
limit: 100
}
}
}
watch: {
dates: {
handler: date => {
console.log(this.params)
if (date.start) {
this.params.from = moment(date.start, "YYYY/MM/DD")
}
if (date.end) {
this.params.to = moment(date.end, "YYYY/MM/DD")
}
},
deep: true
}
}
When I set an input for the dates variable in the view template, I get an undefined for this.params in the console log, and I get an error for trying to set this.params.from. So I tried accessing it using a method:
methods: {
printParams() {
console.log(this.params)
}
}
calling it in the view template, it correctly resolves the params object.
Am I missing something here?
To avoid additional binding, just avoid using the arrow function syntax here.Instead go with ES6 Object shorthands:
watch: {
dates: {
handler(date) {
console.log(this.params)
if (date.start) {
this.params.from = moment(date.start, "YYYY/MM/DD")
}
if (date.end) {
this.params.to = moment(date.end, "YYYY/MM/DD")
}
},
deep: true
}
}
Now this will be bound to the correct context by default.
Let's try bind this to your handler
handler(date) {
console.log(this.params)
if (date.start) {
this.params.from = moment(date.start, "YYYY/MM/DD")
}
if (date.end) {
this.params.to = moment(date.end, "YYYY/MM/DD")
}
}.bind(this)

Update data property / object in vue.js

is there a way I can programmatically update the data object / property in vue.js? For example, when my component loads, my data object is:
data: function () {
return {
cars: true,
}
}
And after an event is triggered, I want the data object to look like:
data: function () {
return {
cars: true,
planes: true
}
}
I tried:
<script>
module.exports = {
data: function () {
return {
cars: true
}
},
methods: {
click_me: function () {
this.set(this.planes, true);
}
},
props: []
}
</script>
But this gives me the error this.set is not a function. Can someone help?
Thanks in advance!
Vue does not allow dynamically adding new root-level reactive properties to an already created instance. However, it’s possible to add reactive properties to a nested object, So you may create an object and add a new property like that:
data: function () {
return {
someObject:{
cars: true,
}
}
and add the property with the vm.$set method:
methods: {
click_me: function () {
this.$set(this.someObject, 'planes', true)
}
}
for vue 1.x use Vue.set(this.someObject, 'planes', true)
reactivity

Categories