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.
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
I am retrieving data from the Vuex Store. I first of all want to check of the array is present in the Vuex Store, after that I want to check if the noProducts object at index 0 is not present.
The reason for this is that tweakwiseSortedProducts is used for both products and a no Products boolean to react to in the front-end
tweakwiseHasProducts () {
if (this.$store.state.tweakwise?.tweakwiseSortedProducts) {
return (
this.$store.state.tweakwise.tweakwiseSortedProducts[0].noProducts ===
false
);
}
return false;
},
My front-end currently, often, returns:
this.$store.state.tweakwise.tweakwiseSortedProducts[0] is undefined
In the console.
This happens because tweakwiseSortedProducts is not undified but an empty list. You can try:
tweakwiseHasProducts () {
if (this.$store.state.tweakwise?.tweakwiseSortedProducts?.length !== 0) {
return (
this.$store.state.tweakwise.tweakwiseSortedProducts[0].noProducts ===
false
);
}
return false;
},
or just:
tweakwiseHasProducts () {
return this.$store.state.tweakwise?.tweakwiseSortedProducts[0]?.noProducts === false;
},
which will be false if any of this elements is undefined, or true if noProducts is really false
It is recommended to use getter when calling a value in Vuex.
Please refer to the following.
getters: {
getTweakwiseSortedProducts: (state: any) => {
return state.tweakwise?.tweakwiseSortedProducts || [];
},
},
tweakwiseHasProducts () {
this.$store.getters.getTweakwiseSortedProducts.length ? true : false;
}
I have a react component with following state
state = {
SandD: true,
Cancellation: false,
Payments: false,
EandR: false,
InternationalShipping: false,
ExpressDelievery: false
}
I want at a time only one selected state to be true and rest to be false. for that I was thinking the following logic
currentActiveState = (value) => {
for ( i=0; i<this.state.length; i++) {
console.log(this.state[i])
if(this.state[i] == value) {
console.log(this.state[i])
} else {
this.setState(this.state[i]: false)
}
}
}
Here value is the property of the state which need to have a value and rest everything should be false..
For example if Payments is true then everything else should be false..
Can someone help me in fixing the above logic or tell me how I can solve it?
Try giving this a shot:
currentActiveState = (value) => {
const newState = {};
Object.keys(this.state).forEach(key => {
newState[key] = key === value;
})
this.setState(newState);
}
Let's say I have a component which repeats with a v-for loop like so:
<hotel v-for="(hotel, index) in hotels"></hotel>
And my hotels array would look like so:
[
{
name: false
},
{
name: false
},
{
name: true
},
{
name: true
}
]
How could I perform an action when my v-for loop encounters the property name set to true only on the very first time it encounters this truthy property?
I know I could probably cache a property somewhere and only run something once and not run again if it has been set but this does not feel efficient.
Use a computed to make a copy of hotels where the first one has an isFirst property.
computed: {
hotelsWithFirstMarked() {
var result = this.hotels.slice();
var first = result.find(x => x.name);
if (first) {
first.isFirst = true;
}
return result;
}
}
Just use computed source
HTML
<div v-for="(hotel, index) in renderHotels"></div>
JS
export default {
name: 'message',
data () {
return{
hotels:[
{
name: false
},
{
name: false
},
{
name: true
},
{
name: true
}
] ,
wasFirst : false
}
},
methods:{
},
computed:{
renderHotels(){
return this.hotels.map((hotel)=>{
if(hotel.name && !this.wasFirst){
this.wasFirst = true;
alert('First True');
console.log(hotel);
}
})
}
}
}
Best way is to use filter function.
data() {
return {
firstTime: false,
};
},
filters: {
myFunc: function (hotel) {
if (hotel.name && !this.firstTime) {
//perform some action
this.firstTime = true;
}
}
}
<hotel v-for="(hotel, index) in hotels | myFunc"></hotel>