Array is undefined from Veux Store - javascript

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;
}

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.

Undefined for firebase subscribe

I need to return array of items to display it on HomePage
I tried to modify the code, but nothing works. I feel like I need a slight change for my code
getItems(segmentType): any {
return this.db.collection('items', ref => ref.where('type', '==',
segmentType)).valueChanges();
}
getItemsBySearchQuery(segmentType, queryText): any[] {
this.getItems(segmentType).subscribe(items => {
this.itemsContainer = items;
this.filteredItems = this.itemsContainer.filter((v) => {
if (v.title && queryText) {
if (v.title.toLowerCase().indexOf(queryText.toLowerCase()) > -1) {
return true;
}
return false;
}
});
});
//TODO: THIS IS STILL UNDEFINED
return this.filteredItems;
}
Ion-List with filtered elements, but I have "undefined", because code returns the array too early

Vue JS filter results based on checkbox array

I've built a Vue JS search component to search and filter a list of properties for a property website. The search component is listed on every page, so it makes sense for me to use a URL search query which takes me to the main properties page and then use something like this.$route.query.search to get the value from my query and store in a variable.
The property data is coming from a JSON file which essentially looks like this:
{
"data": [
{
"id": 12,
"sold": false,
"isFeatured": true,
"slug": "green-park-talbot-green-cf72-8rb",
"address": "Green Park, Talbot Green CF72 8RB",
"county": "Rhondda Cynon Taf",
"price": "695000",
"features": ["Modern fitted kitchen", "Integrated appliances", "Front and rear gardens"],
"type": "Semi-Detached",
"bedrooms": "3"
}
}
My search query would be something like this:
/properties/?search=Address%20Here&type=Apartment&bedrooms=2&county=Maesteg
Which then would filter each thing.
How this works is quite simple, inside my data object I have my variables which get each query and store them as follows:
data () {
return {
searchAddress: this.$route.query.search,
searchType: this.$route.query.type,
searchBedrooms: this.$route.query.bedrooms,
searchCounty: this.$route.query.county
}
}
And then I have a filter inside the computed area called filteredProperties which filters down the properties inside the v-for which isn't necessary to show here:
computed: {
filteredProperties: function(){
return this.properties.filter((property) => {
return property.address.match(this.searchAddress) && property.type.match(this.searchType) && property.bedrooms.match(this.searchBedrooms) && property.county.match(this.searchCounty)
});
}
}
Now this works absolutely fine and works correctly... however I now need to modify this to instead of having <select> dropdowns which is how you would currently pick the number of bedrooms, or the property type etc, I now need to replace the property type <select> dropdown with checkboxes so that the user can select multiple property types and essentially add that as an array into the URL.
I'm not quite sure how to modify this part of my filter to be able to look for multiple property types:
property.type.match(this.searchType)
Many thanks
UPDATE
I've recently tried updating my computed filter with the following:
computed: {
filteredProperties: function(){
return this.properties.filter((property) => {
return property.address.match(this.searchAddress) &&
this.searchAddress.some(function(val){
return property.search.match(val)
}) &&
property.type.match(this.searchType) &&
this.searchType.some(function(val){
return property.type.match(val)
}) &&
property.bedrooms.match(this.searchBedrooms) &&
this.searchBedrooms.some(function(val){
return property.bedrooms.match(val)
}) &&
property.county.match(this.searchCounty) &&
this.searchCounty.some(function(val){
return property.county.match(val)
})
});
}
}
I need the search to work with and without a URL query.
Also tried an if/else statement:
computed: {
filteredProperties: function(){
return this.properties.filter((property) => {
return property.address.match(this.searchAddress) &&
if (this.searchType.length > 1) {
this.searchType.some(function(val){
return property.type.match(val)
})
} else {
property.type.match(this.searchType)
} &&
property.bedrooms.match(this.searchBedrooms) &&
property.county.match(this.searchCounty)
});
}
}
UPDATE
I got it working by doing the following:
computed: {
filteredProperties: function(){
return this.properties.filter((property) => {
let searchTypeMatch;
if (typeof this.searchType === "object") {
searchTypeMatch = this.searchType.some(function(val){
return property.type.match(val)
})
} else {
searchTypeMatch = property.type.match(this.searchType)
}
return property.address.match(this.searchAddress) &&
searchTypeMatch &&
property.bedrooms.match(this.searchBedrooms) &&
property.county.match(this.searchCounty)
});
}
}
You will have to use JSON for the query parameters in order to serialize/deserialize the arrays.
data () {
return {
searchAddress: this.$route.query.search ? JSON.parse(this.$route.query.search) : [],
searchType: this.$route.query.type ? JSON.parse(this.$route.query.type) : [],
searchBedrooms: this.$route.query.bedrooms ? JSON.parse(this.$route.query.bedrooms) : [],
searchCounty: this.$route.query.county ? JSON.parse(this.$route.query.county) : []
}
}
computed: {
filteredProperties: function()
{
return this.properties.filter((property) =>
{
return (this.searchAddress.length ? this.searchAddress.some((address) =>
{
return property.address.match(address);
}) : true) && (this.searchType.length ? this.searchType.some((type) =>
{
return property.type.match(type);
}) : true) && (this.searchBedrooms.length ? this.searchBedrooms.some((bedrooms) =>
{
return property.bedrooms.match(bedrooms);
}) : true) && (this.searchCounty.length ? this.searchCounty.some((county) =>
{
return property.county.match(county);
}) : true)
});
}
}
Then you send query params like this
this.$router.push("/search?search=" + JSON.stringify(searchArray)
+ "&type=" + JSON.stringify(typeArray)
+ "&bedrooms=" + JSON.stringify(bedroomsArray)
+ "&county=" + JSON.stringify(countyArray)
);
I have not worked with routing in Vue apps but for the following to work you will have to ensure that this.$route.query.search (and the other route properties) is an (are) [](s).
return this.searchAddress.some(function(val) {
return property.address.match(val)
}) &&
this.searchType.some(function(val){
return property.type.match(val)
}) && ...
Let me know if this works for you.
RE-EDITED:
Hi, please change the computed property to the following
computed: {
filteredProperties: function () {
let self = this
let routeConstraints = ['search', 'type', 'bedrooms', 'county'].filter(function (val) {
return self.$route.query[val] !== undefined
})
if (routeConstraints.length === 0) {
return self.properties
} else {
return routeConstraints.reduce(function (acc, val) {
return acc.filter(function (property) {
//basically I am checking if there is some value in the query parameter that matches properties.
if (self.$route.query[val] !== undefined) {
//check if the route parameter is an array (object)
if (typeof this.searchType === "object") {
self.$route.query[val] = [self.$route.query[val]]
}
}
return self.$route.query[val].some(function (item) {
//changed the matching condition to indexOf
return property[val].match(item).length > 0
})
})
}, self.properties)
}
}
}
Basically,
I am trying to check what routes values are set from your checkbox selections
Using it to filter the properties array.
If none are set then all properties are returned.
Hope this works.

Best way to assign property to javascript property

I am using vueJs computed to create options for my component like this:
computed:{
fileOptions() {
let fileOptions = [
{
event:'event',
name:'Abcd',
disabled://based upon some condition,
display://based upon some condition
},
{
event://based upon some condition,
name:'Open Presentation',
disabled://based upon some condition,
display://based upon some condition
},
]
}
}
The event, disabled and display property are based upon multiple conditions.
One way of doing this is by using ternary operator
disabled:this.state.libraryActive=='presentations'?false:true
However, it is easy for one condition but for multiple conditions it becomes difficult.
Any suggestions?
The best practice is to use another computed property:
computed:{
fileOptions() {
let fileOptions = [
{
event:'event',
name:'Abcd',
disabled: this.isAbcdDisabled, // based upon some condition,
display: this.isAbcdVisible // based upon some condition,
},
{
event: this.getEventName, // based upon some condition,
name:'Open Presentation',
disabled: this.getDisabled(this.getEventName), // based upon some condition,
display: this.getVisible(this.getEventName) //based upon some condition
},
]
},
isAbcdDisabled ()
{
return this.state.libraryActive === 'presentations' && !this.admin ? false : true
},
isAbcdVisible ()
{
return true;
},
getEventName ()
{
return this.canEdit ? 'edit' : 'add';
}
},
methods:
{
getDisabled (eventName)
{
switch(eventName)
{
case 'edit': return false;
case 'add': return true;
default: return false;
}
},
getVisible (eventName)
{
switch(eventName)
{
case 'edit': return true;
case 'add': return true;
default: return false;
}
},
}
You don't need to use ternary operator, you could make it much easier:
disabled: this.state.libraryActive !== 'presentations'
It will return false if this.state.libraryActive is equal to 'presentations' and true otherwise.

Changing Value of State dynamically in React

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);
}

Categories