NUXT - How to better refactor computed properties - javascript

I have a NUXT project, and some data I have will sometimes not exists.
However, the computed properties is getting quite long, and I feel it isn't the best practice some reason. Is there a way to shorten this, or somehow make the computed properties dynamic by passing in arguments without breaking the site if it doesn't exists?
export default {
props: {
halfWidth: {
type: Boolean,
default: false
},
slice: {
type: Object,
default: () => {}
}
},
computed: {
title() {
return this.slice?.data?.shopifyproduct?.title
},
price() {
return this.slice?.data?.shopifyproduct?.variants[0]?.price
},
image() {
return this.slice?.data?.shopifyproduct?.image?.src
},
alt() {
return this.slice?.data?.shopifyproduct?.image?.alt
},
desc() {
return this.slice?.data?.short_description[0]?.text
},
handle() {
return this.slice?.data?.shopifyproduct?.handle
}
}
}
</script>

Since most of your computed propeerties depend on shopifyProduct, you ccan create a meethod to return that
export default {
props: {
halfWidth: {
type: Boolean,
default: false
},
slice: {
type: Object,
default: () => {}
}
},
methods {
getData() {
return this.slice?.data;
},
getShopifyProduct() {
return this.getData()?.shopifyproduct;
},
},
computed: {
title() {
return this.getShopifyProduct()?.title
},
price() {
return this.getShopifyProduct()?.variants[0]?.price
},
image() {
return this.getShopifyProduct()?.image?.src
},
alt() {
return this.getShopifyProduct()?.image?.alt
},
desc() {
return this.getData()?.short_description[0]?.text
},
handle() {
return this.getShopifyProduct()?.handle
}
}
}
</script>

try to use lodash/get https://lodash.com/docs/4.17.15#get
import _get from 'lodash/get'
computed: {
title() {
return _get(this.slice, 'data.shopifyproduct.title')
},
title() {
return _get(this.slice, 'data.shopifyproduct.variants.0.price')
},
}
OR
methods: {
getShopifyproduct(key) {
return _get(this.slice.shopifyproduct, key)
}
}
function call
getShopifyproduct('variants.0.price')

Related

Vue.js 2: return statement returning multiple values in parentheses (watch & computed)

EDITED: Added more code for context as requested by the community.
I have a Vue.js 2 app that I am now maintaining where there is a computed property. This computed property has a return statement that is returning several data properties in a comma-separated format, and the entire group is wrapped in parentheses.
I tried looking at both Javascript and Vue.js 2 documentation, and I can't figure out what this is doing. In Javascript, if you wanted to return multiple values, you would return an array with the values or an object with multiple properties.
Here is the other weird thing. This "compoundFormProperty" computed property isn't used in the component template! There is a watcher on this "compoundFormProperty" property. I searched the entire code base, and "compoundFormProperty" only exists in this one component. Since it's not used by the component template, I think it is being used to track some internal state (I think it's used to check if the form is valid). The rest of the computed properties have get() and set() calls, so they appear to reference a Vuex store that stores the data.
<script>
import { mapState } from "vuex";
export default {
name: "DeliveryAddress",
components: {},
props: {
countries: Array,
usStates: Array,
canStates: Array,
errorsCount: Number,
isEdit: Boolean,
selectedAddress: Boolean,
sameAsBilling: Boolean
},
data: function() {
return {
sameAddress: false,
saveAddress: false,
nameEmpty: true,
nameTouched: false,
addressEmpty: true,
addressTouched: false,
cityEmpty: true,
cityTouched: false,
postalTouched: false,
invalidDeliveryPostal: true,
invalidBillingPostal: true,
formTouched: false,
storedInfo: {},
cancel: false,
editInvalid: false,
saveInProgress: false,
sameBilling: this.sameAsBilling
};
},
computed: {
formInvalid: {
get() {
if (this.selectedAddress) {
return false;
}
return (
this.formTouched === false ||
this.nameEmpty ||
this.cityEmpty ||
this.addressEmpty ||
this.invalidDeliveryPostal
);
}
},
compoundFormProperty: function() {
return (
this.deliveryName,
this.deliveryCity,
this.deliveryAddress,
this.deliveryState,
this.deliveryCountry,
this.deliveryPostal,
Date.now()
);
},
...mapState([
"name",
"city",
"address",
"addressTwo",
"country",
"state",
"postal",
"cartObj",
"accountObj"
]),
cartObj: {
get() {
return this.$store.state.cartObj;
},
set(value) {
this.$store.commit("setCart", value);
}
},
deliveryName: {
get() {
return this.$store.state.deliveryName;
},
set(value) {
this.$store.commit("setDeliveryName", value);
}
},
deliveryAddress: {
get() {
return this.$store.state.deliveryAddress;
},
set(value) {
this.$store.commit("setDeliveryAddress", value);
}
},
deliveryAddressTwo: {
get() {
return this.$store.state.deliveryAddressTwo;
},
set(value) {
this.$store.commit("setDeliveryAddressTwo", value);
}
},
deliveryCity: {
get() {
return this.$store.state.deliveryCity;
},
set(value) {
this.$store.commit("setDeliveryCity", value);
}
},
deliveryState: {
get() {
return this.$store.state.deliveryState;
},
set(value) {
this.$store.commit("setDeliveryState", value);
}
},
deliveryCountry: {
get() {
return this.$store.state.deliveryCountry;
},
set(value) {
this.$store.commit("setDeliveryCountry", value);
}
},
deliveryPostal: {
get() {
return this.$store.state.deliveryPostal;
},
set(value) {
this.$store.commit("setDeliveryPostal", value);
}
},
accountObj: {
get() {
return this.$store.state.accountObj;
},
set(value) {
this.$store.commit("setAccount", value);
}
}
},
watch: {
compoundFormProperty() {
if (this.sameBilling || this.selectedAddress) {
this.$emit("change", { errors: false });
} else if (
this.deliveryName &&
this.deliveryCity &&
this.deliveryAddress &&
this.deliveryState &&
this.deliveryCountry &&
this.invalidDeliveryPostal === false
) {
this.$emit("change", { errors: false });
} else if (
this.isEdit &&
this.deliveryName &&
this.deliveryCity &&
this.deliveryAddress &&
/^[-/\w ]{2,12}$/.test(this.deliveryPostal)
) {
this.editInvalid = false;
} else {
this.$emit("change", { errors: true });
this.editInvalid = true;
}
}
//Other watchers
}
}
</script>
JavaScript functions can't return multiple values. Expressions delimited by comma operators are evaluated to the last one, i.e. compoundFormProperty returns Date.now().
It could be a mistake with comma-separated expression being returned instead of an array. But here it looks like a hack to trigger reactivity in computed property.
Due to how Vue reactivity works, deliveryName, etc. properties are marked to be tracked when accessed on this. compoundFormProperty will run on any changes in instance properties that are accessed inside computed property.
The explicit and not hacky way is to rewrite this as a collection watcher:
data() {
return { compoundFormProperty: null, ... }
},
created() {
this.$watch(
['deliveryName', ...],
() => {
this.compoundFormProperty = Date.now()
},
{ immediate: true }
);
}
The result may be somewhat different depending on the use, a watcher with immediate causes compoundFormProperty value to be assigned immediately, while computed property is evaluated the first time when it's accessed.
UPD: the combination of compoundFormProperty computed property and watcher in original post is a very hacky way to write this.$watch with array input, likely because a developer wasn't aware of it, or the code derived from Vue 1.x that didn't have specific functionality to shallowly watch collections. Date.now() value is efficiently ignored and used to not cache a computed.

Have variable changes on button click but initially set using localStorage in vue

I am trying to setup a button that changes a data value in Vue but also have it set using localStorage initally. This way I can have it keep the previous state it was in before a page refresh. Below is the code I'm using and I'm able to get it to work but know that it would be preferable to use the computed section but haven't been able to get that to work properly.
Would anyone know what is going wrong?
My button is triggered using the testing method and the variable in question is isGrid.
export default {
data() {
return {
option: 'default',
}
},
components: {
FileUploader,
},
mixins: [
visibilitiesMixin,
settingsMixin
],
props: {
vehicleId: {
type: Number,
required: true,
default: null,
}
},
computed: {
...mapState([
'isLoading',
'images',
'fallbackImageChecks',
'selectedImages'
]),
isGrid: {
get() {
return localStorage.getItem('isGrid');
},
},
imagesVModel: {
get() {
return this.images;
},
set(images) {
this.setImages(images);
}
},
selectedImagesVModel: {
get() {
return this.selectedImages;
},
set(images) {
this.setSelectedImages(images);
}
},
removeBgEnabled() {
return this.setting('nexus_integration_removebg_enabled') === 'enabled';
},
},
mounted() {
this.loadImages(this.vehicleId);
},
methods: {
testing() {
if (this.isGrid === 'false' || this.isGrid === false) {
localStorage.setItem('isGrid', true);
this.isGrid = true;
console.log(this.isGrid);
console.log(localStorage.getItem('isGrid'));
} else {
localStorage.setItem('isGrid', false);
this.isGrid = false;
console.log('b');
console.log(this.isGrid);
console.log(localStorage.getItem('isGrid'));
}
},
}
I suggest you use vuex with vuex-persistedstate.
https://www.npmjs.com/package/vuex-persistedstate

Scope of locally defined variables in vue

In this sample:
<template>
<div>
<p
v-for="prop in receivedPropsLocal"
:key="prop.id"
>
{{prop}}
</p>
</div>
</template>
<script>
export default {
name: "PropsReceiver",
props: {
receivedProps: {
required: true,
type: Array,
default() {
return [];
},
},
},
data() {
return {
receivedPropsLocal: Array,
};
},
methods: {
},
watch: {
receivedProps: {
deep: true,
handler(val) {
let tmp = Object.entries(Object.assign({}, val));
this.receivedPropsLocal = tmp;
},
},
},
computed: {
getReceivedPropsLocal: {
get() {
if (!this.receivedPropsLocal) {
let tmp = Object.entries(Object.assign({}, this.receivedProps));
this.receivedPropsLocal = tmp;
return this.receivedPropsLocal;
}
return this.receivedPropsLocal;
},
set(value) {
this.receivedPropsLocal = value;
},
},
},
};
</script>
what's the scope of tmp? Is it handled similarly to other entries in data() or not? or it doesn't matter.
I believe tmpis only accessible from inside the handler function since you used let to declare it.
You should declare it directly in the data object to use it anywhere in the component.

Detect changes in JSON on a javascript side

I am receiving data in JSON from PHP and I want to detect if JSON have changed on Javascript side, not template side. Does anybody have solution? I am attaching a code.
export default {
name: 'dashboard_invoices',
data() {
return {
invoices: [],
newInvoices: [],
}
},
methods: {
loadDataInterval: function() {
this.$http.get('http://127.0.0.1/pdaserwis-crm/api/vue/vue?action=order_table').then(function (response) {
this.newInvoices = response.data;
if(this.newInvoices != this.invoices) {
// alert('Dodano nowa fakture');
this.invoices = this.newInvoices;
}
})
},
loadData: function() {
this.$http.get('http://website.pl').then(function (response) {
this.invoices = response.data;
})
}
},
created: function () {
this.loadData();
setInterval(function () {
this.loadDataInterval();
}.bind(this), 3000);
}
}
I want to catch if invoices have changed and view appropriate alert for that.
The problem solved. It took to compare both arrays with deep-equal by watch handler.
watch: {
invoices: {
handler(val, oldVal)
{
if(!(deepEqual(val, oldVal))) {
console.log('elo');
}
}
}
}

Vue.js. Data property undefined

I'm trying to access my data property in my Vue.js component. Looks like I'm missing something obvious.
Here is a short version of my code. StoreFilter.vue is a wrapper for matfish2/vue-tables-2.
<template>
<store-filter :selected.sync="storeIds"></store-filter>
</template>
<script>
import StoreFilter from './Filters/StoreFilter';
export default {
components: {
StoreFilter
},
data() {
return {
options : {
requestFunction(data) {
console.log(this.storeIds); //undefined
return axios.get('/api/orders', {
params: data
}).catch(function (e) {
this.dispatch('error', e);
}.bind(this));
},
},
storeIds: [],
}
},
watch : {
storeIds(storeIds) {
this.refreshTable();
}
},
methods : {
refreshTable() {
this.$refs.table.refresh();
}
}
}
</script>
How to get storeIds from requestFunction?
Use a closure, see rewrite below.
data() {
let dataHolder = {};
dataHolder.storeIds = [];
dataHolder.options = {
requestFunction(data) {
// closure
console.log(dataHolder.storeIds); // not undefined
return axios.get('/api/orders', {
params: data
}).catch(function (e) {
this.dispatch('error', e);
}.bind(this));
}
}
return dataHolder;
}
I recommend using the created() way to handle this.
export default {
// whatever you got here
data () {
return {
options: {}
}
},
created () {
axios.get('/api/orders', { some: params }).then(response => this.options = response.data)
}
}

Categories