I am trying to get a Vue.js computed property to do some reconfiguring of data and when I do it inside the computed property I get an error about side effects. I looked it up on here and found that using a method is the correct way of doing this. Now when trying to use a method it results in error messages in the console about it can't read the property of 'units' from selectedBuilding.
What I am trying to figure out is how to have it so the units array is populated with the correct data depending on the computed return value
Below is the code stripped down for the script section of my Vue.js file.
export default {
name: "Form",
data: () => ({
building: {},
buildings: [
{
"units": [ // Bunch of data in here that isn't important//],
"buildingID": "<an ID according to a custom format>",
}],
units: [],
}),
methods: {
formatUnits: function(selectedBuildingID) {
let selectedBuilding = this.buildings.find(building => building.buildingID === selectedBuildingID)
this.units = selectedBuilding.units
return true
}
},
computed: {
useExistingBuilding() {
if(this.building === 'New') {return true}
else {
this.formatUnits(this.building)
return false
}
},
}
};
</script>
Error message:
TypeError: Cannot read property 'units' of undefined
Since this.building has no buildingID (thus, undefined), and there is no item in buildings that has undefined as the value for buildingID, it fails.
This is because the find method will return undefined when the condition couldn't be satisfied.
The find() method returns the value of the first element in the provided array that satisfies the provided testing function. If no values satisfy the testing function, undefined is returned.
MDN Reference
In the computed property you are passing the whole building rather than the id, whereas in your method (in find) you are comparing the ids.
You need to pass the building id to your method.
Also in your data you are declaring building as an object, and then in your computed property, you are asserting whether building equals New which is a string.
I would advise the first thing you have to do is rethink your data types and the flow of your data between the template, computed properties, methods and data.
Related
I have an empty products array and users can add new products if they want. My goal is I want to save the last productKey and save it in a Vuex state.
//data
products: []
//method
addNewProduct() {
this.products.push({
productKey: '',
productName: ''
})
},
I want to get the key of the last added product (last object). I've tried this way
lastKey() {
return this.products[this.products.length-1].productKey
}
This doesn't work as it says cannot read properties of undefined (reading 'productKey'). Any help would be greatly appreciated. thank you.
All state-related (including computed properties) run before hook created .Because products is initially empty so you try to access productKey of last item will result cannot read properties of undefined (reading 'productKey'). You can use optional chaining to check possibility exists.
lastKey() {
return this.products[this.products.length-1]?.productKey
}
As products is an empty array inside data object and you are assigning the data in it later on by using mounted() or methods.
Initially, computed property always execute before mounted life cycle hook to create the property and at that time products is an empty array. Hence, It is giving cannot read properties of undefined (reading 'productKey') error.
To get rid from this error, You can use Optional chaining (?.) which enables you to read the value of a property located deep within a chain of connected objects without having to check that each reference in the chain is valid.
Live demo :
new Vue({
el: '#app',
data: {
products: []
},
mounted() {
console.log('mounted call'); // This calls
this.products.push({
productKey: 'key1',
productName: 'name1'
})
},
computed: {
lastKey: function() {
console.log('computed call'); // This calls before mounted life cycle hook to create the lastKey property and it will call again after mounted() as there is a change in the products array.
return this.products[this.products.length - 1]?.productKey;
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<p>{{ lastKey }}</p>
</div>
I think your code is already correct. this happened because products array is empty
I'm using Vue Froala to do the image upload using the Froala config options which are basically an object added to the Vue data property. Something like this:
data() {
return {
config: {
imageUploadURL: '/api/image/store',
imageUploadParam: 'image',
imageUploadParams: {id: this.id} //this is the dynamic ID
}
}
}
When I upload an image and those config options are triggered, the ID that I'm trying to pass is always undefined. I guess it is because when I first load the page, the Vue data properties are initialized at the very beginning and the dynamic ID that I'm retrieving from a prop, is not yet available.
I tried assigning the data property imageUploadParams a computed property that would return the ID but was still undefined
If I use a watcher to assign the ID afterwards, I get null. Here is what I get in the POST code (php)
+request: Symfony\Component\HttpFoundation\ParameterBag {#45
#parameters: array:1 [
"id" => null
]
}
Any idea?
You could use a watch:
data() {
return {
config: {
imageUploadURL: '/api/image/store',
imageUploadParam: 'image',
imageUploadParams: { id: '' }
}
}
},
watch: {
id(value) {
this.config.imageUploadParams.id = value;
}
}
Now whenever id changes, config.imageUploadParams will be set.
Assigning a variable in JavaScript sets the value at the time of the assignment. Since your id is empty when it's assigned, there's no reference to use later to access future async properties. Assigning a computed won't help for the same reason.
Using watch for async, and computed for sync is a good general rule
Edit from chat
It seems <froala> isn't able to respond to your reactive data and is only using the initial config value. Put a v-if="id" on the component to prevent it from being created until the id is ready:
<froala v-if="id">
I want to get the initials of the full name using computed properties but it returns undefined and tries to get the initials before it renders the full name as shown below
computed: {
user: function () {
console.log(this.$store.state.user.name);
//return this.$store.state.username.match(/[A-Z]/g).slice(0, 2).join('');
}
}
How to get this to work? like it doesn't call match function unless the full name is rendered?
The way the computed property is running twice, is once when the app is created, then once your data updates, running twice.
Best way to handle this is wherever your data is to change the computed property to check if the data is there, like so:
if(this.$store.state.user.name) return this.$store.state.user.name
Or if you are placing your computed property to the template, use a v-if:
v-if="$store.state.user.name"
You can use Optional Chaining to handle undefined cases within your return statement.
MDN
computed: {
user: function () {
return this.$store.state?.user?.name?.match(/[A-Z]/g).slice(0, 2).join('');
}
}
I have a form field that has the following generic autocomplete:
<generic-autocomplete
v-model='c.heuristicId'
v-on:change='heuristicSelected(i, $event)'
label='Fuente de datos'
:apiFunction='heuristicsFetchFunction'
:disabled='disabled'/>
When saving the form and sending it with this field in blank heuristicIdgets sent as nullwhich is what is desired.
However, when selecting a value from the generic autocomplete and then deleting it, heuristicIdgets sent with the id of the previously selected value.
Inside the function that calls axios I'm logging console.log("heuristicId to be sent: " + campaign.heuristicId);and undefinedgets logged in both scenarios.
EDIT: After inquiring a little bit more I believe that the error might be in one of <generic-autocomplete/>'s computed properties:
isValid: function () {
// this checks that the name in display equals the object selected in
// memory so no invalid labels are displayed while a valid object is 'selected'
let vc = this
let result = false
if (vc.object) {
result = vc.objectName === vc.object.name
console.log('generic-autocomplete#isValid', result)
}
return result
}
An empty vc.objectwill return a falseresult and I haven't been able to satisfactorily handle that.
After logging a really complex control flow I finally got to the point where I knew which properties I had to mutate. However, it turns out that in Vue 2 mutating inline properties is an anti-pattern. The capability of mutating inline props was deprecated from the previous Vue version in order to be consistent with the library's fully reactive paradigm. So what happens is that if you try to mutate an inline property, when the component re-renders, such property will be set back to its default (or previous) value.
This is solved by adding a computed property that's a copy from the property that you want to mutate.
//Somewhere else in the code the property is defined
property: null
//Add the computed property
data: function () {
return {
mutableProperty: this.property,
}
}
I have getters defined within the property definition of my model, they look like this:
timeReported: {
type: DataTypes.DATE,
defaultValue: Sequelize.NOW,
get() {
const input = this.getDataValue('timeReported')
const output = moment(input).valueOf()
return output
},
set(input) {
var output = moment(input).toDate()
this.setDataValue('timeReported', output)
},
validate: {
isBefore: moment().format('YYYY-MM-DD')
}
}
I have tried adding the getter to getterMethods in the options instead of within the property:
getterMethods: {
timeReported: function () {
debugger
const input = this.getDataValue('timeReported')
const output = moment(input).valueOf()
return output
}
}
I find that the setters are executed correctly when I save because I use timestamps as the request payload. I also find that validations work correctly.
Interestingly the afterFind hook is called correctly and it does show that customGetters have been registered as existing on my model.
However, when I call the find method on my model like this:
const {dataValues} = yield models.Reporting.findOne({
where: {
reportNo: reportNo
}
})
My getter methods are not invoked to transform my data for presentation. How do I invoke these methods on the data fetched from the db before I can access it.
I have tried adding the raw:false property to the queryOptions along with where. That did not work.
NOTE: I fetch my data using co, I have to retrieve the dataValues property from the model. None of the examples seem to be using the property.
When using Sequelize,one should not use dataValues property directly as I did. This will give me access to the underlying data stored in the table without invoking the getters.
To invoke all the getter functions on the entire instance:
const data = instance.get()
This will also populate any virtual fields that one has defined in the model.
To invoke the getter for an individual property:
const propValue = instance.get('timeReported')