VueJS: Passing arguments to a computed property from methods object [duplicate] - javascript

is this possible to pass parameter in computed properties in Vue.Js. I can see when having getters/setter using computed, they can take a parameter and assign it to a variable. like here from documentation:
computed: {
fullName: {
// getter
get: function () {
return this.firstName + ' ' + this.lastName
},
// setter
set: function (newValue) {
var names = newValue.split(' ')
this.firstName = names[0]
this.lastName = names[names.length - 1]
}
}
}
Is this also possible:
computed: {
fullName: function (salut) {
return salut + ' ' + this.firstName + ' ' + this.lastName
}
}
Where computed property takes an argument and returns the desired output. However, when I try this, I am getting this error:
vue.common.js:2250 Uncaught TypeError: fullName is not a function(…)
Should I be using methods for such cases?

Most probably you want to use a method
<span>{{ fullName('Hi') }}</span>
methods: {
fullName(salut) {
return `${salut} ${this.firstName} ${this.lastName}`
}
}
Longer explanation
Technically you can use a computed property with a parameter like this:
computed: {
fullName() {
return salut => `${salut} ${this.firstName} ${this.lastName}`
}
}
(Thanks Unirgy for the base code for this.)
The difference between a computed property and a method is that computed properties are cached and change only when their dependencies change. A method will evaluate every time it's called.
If you need parameters, there are usually no benefits of using a computed property function over a method in such a case. Though it allows you to have a parametrized getter function bound to the Vue instance, you lose caching so not really any gain there, in fact, you may break reactivity (AFAIU). You can read more about this in Vue documentation https://v2.vuejs.org/v2/guide/computed.html#Computed-Caching-vs-Methods
The only useful situation is when you have to use a getter and need to have it parametrized. For instance, this situation happens in Vuex. In Vuex it's the only way to synchronously get parametrized result from the store (actions are async). Thus this approach is listed by official Vuex documentation for its getters
https://vuex.vuejs.org/guide/getters.html#method-style-access

You can use methods, but I prefer still to use computed properties instead of methods, if they're not mutating data or do not have external effects.
You can pass arguments to computed properties this way (not documented, but suggested by maintainers, don't remember where):
computed: {
fullName: function () {
var vm = this;
return function (salut) {
return salut + ' ' + vm.firstName + ' ' + vm.lastName;
};
}
}
EDIT: Please do not use this solution, it only complicates code without any benefits.

Well, technically speaking we can pass a parameter to a computed function, the same way we can pass a parameter to a getter function in vuex. Such a function is a function that returns a function.
For instance, in the getters of a store:
{
itemById: function(state) {
return (id) => state.itemPool[id];
}
}
This getter can be mapped to the computed functions of a component:
computed: {
...mapGetters([
'ids',
'itemById'
])
}
And we can use this computed function in our template as follows:
<div v-for="id in ids" :key="id">{{itemById(id).description}}</div>
We can apply the same approach to create a computed method that takes a parameter.
computed: {
...mapGetters([
'ids',
'itemById'
]),
descriptionById: function() {
return (id) => this.itemById(id).description;
}
}
And use it in our template:
<div v-for="id in ids" :key="id">{{descriptionById(id)}}</div>
This being said, I'm not saying here that it's the right way of doing things with Vue.
However, I could observe that when the item with the specified ID is mutated in the store, the view does refresh its contents automatically with the new properties of this item (the binding seems to be working just fine).

computed: {
fullName: (app)=> (salut)=> {
return salut + ' ' + this.firstName + ' ' + this.lastName
}
}
when you want use
<p>{{fullName('your salut')}}</p>

[Vue2] Filters are a functionality provided by Vue components that let you apply formatting and transformations to any part of your template dynamic data.
They don’t change a component’s data or anything, but they only affect the output.
Say you are printing a name:
new Vue({
el: '#container',
data() {
return {
name: 'Maria',
lastname: 'Silva'
}
},
filters: {
prepend: (name, lastname, prefix) => {
return `${prefix} ${name} ${lastname}`
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="container">
<p>{{ name, lastname | prepend('Hello') }}!</p>
</div>
Notice the syntax to apply a filter, which is | filterName. If you're familiar with Unix, that's the Unix pipe operator, which is used to pass the output of an operation as an input to the next one.
The filters property of the component is an object.
A single filter is a function that accepts a value and returns another value.
The returned value is the one that’s actually printed in the Vue.js template.
Filters were removed in Vue3

You can pass parameters but either it is not a vue.js way or the way you are doing is wrong.
However there are cases when you need to do so.I am going to show you a simple example passing value to computed property using getter and setter.
<template>
<div>
Your name is {{get_name}} <!-- John Doe at the beginning -->
<button #click="name = 'Roland'">Change it</button>
</div>
</template>
And the script
export default {
data: () => ({
name: 'John Doe'
}),
computed:{
get_name: {
get () {
return this.name
},
set (new_name) {
this.name = new_name
}
},
}
}
When the button clicked we are passing to computed property the name 'Roland' and in set() we are changing the name from 'John Doe' to 'Roland'.
Below there is a common use case when computed is used with getter and setter.
Say you have the follow vuex store:
export default new Vuex.Store({
state: {
name: 'John Doe'
},
getters: {
get_name: state => state.name
},
mutations: {
set_name: (state, payload) => state.name = payload
},
})
And in your component you want to add v-model to an input but using the vuex store.
<template>
<div>
<input type="text" v-model="get_name">
{{get_name}}
</div>
</template>
<script>
export default {
computed:{
get_name: {
get () {
return this.$store.getters.get_name
},
set (new_name) {
this.$store.commit('set_name', new_name)
}
},
}
}
</script>

You can also pass arguments to getters by returning a function. This is particularly useful when you want to query an array in the store:
getters: {
// ...
getTodoById: (state) => (id) => {
return state.todos.find(todo => todo.id === id)
}
}
store.getters.getTodoById(2) // -> { id: 2, text: '...', done: false }
Note that getters accessed via methods will run each time you call them, and the result is not cached.
That is called Method-Style Access and it is documented on the Vue.js docs.

I'd like to first reiterate the previous caveats that using computed (which is cached) with a parameter simply makes the computed not cached, effectively just making it a method.
However, that being said, here are all the variations that I can think of which may have edge-cases for use. If you cut & paste this into a demo app it should be clear what is going on:
<template>
<div>
<div style="background: violet;"> someData, regularComputed: {{ someData }}, {{ regularComputed }} </div>
<div style="background: cornflowerblue;"> someComputedWithParameterOneLine: {{ someComputedWithParameterOneLine('hello') }} </div>
<div style="background: lightgreen;"> someComputedWithParameterMultiLine: {{ someComputedWithParameterMultiLine('Yo') }} </div>
<div style="background: yellow"> someComputedUsingGetterSetterWithParameterMultiLine: {{ someComputedUsingGetterSetterWithParameterMultiLine('Tadah!') }} </div>
<div>
<div style="background: orangered;"> inputData: {{ inputData }} </div>
<input v-model="inputData" />
<button #click="someComputedUsingGetterSetterWithParameterMultiLine = inputData">
Update 'someComputedUsingGetterSetterWithParameterMultiLine' with 'inputData'.
</button>
</div>
<div style="background: red"> newConcatenatedString: {{ newConcatenatedString }} </div>
</div>
</template>
<script>
export default {
data() {
return {
someData: 'yo',
inputData: '',
newConcatenatedString: ''
}
},
computed: {
regularComputed(){
return 'dude.'
},
someComputedWithParameterOneLine(){
return (theParam) => `The following is the Parameter from *One* Line Arrow Function >>> ${theParam}`
},
someComputedWithParameterMultiLine(){
return (theParam) => {
return `The following is the Parameter from *Multi* Line Arrow Function >>> ${theParam}`
}
},
// NOTICE that Computed with GETTER/SETTER is now an Object, that has 2 methods, get() and set(), so after the name of the computed we use : instead of ()
// thus we do: "someComputedUsingGetterSetterWithParameterMultiLine: {...}" NOT "someComputedUsingGetterSetterWithParameterMultiLine(){...}"
someComputedUsingGetterSetterWithParameterMultiLine: {
get () {
return (theParam) => {
return `As part of the computed GETTER/SETTER, the following is inside get() which receives a Parameter (using a multi-line Arrow Function) >>> ${theParam}`
}
},
set(newSetValue) {
console.log('Accessing get() from within the set()', this.someComputedUsingGetterSetterWithParameterMultiLine('hello from inside the Setter, using the Getter.'))
console.log('Accessing newSetValue in set() >>>>', JSON.stringify(newSetValue))
this.newConcatenatedString = `**(1)${this.someComputedUsingGetterSetterWithParameterMultiLine('hello from inside the Setter, using the Getter.')}** This is a concatenation of get() value that had a Parameter, with newSetValue **(2)${newSetValue}** that came into the set().`
}
},
},
}
</script>

Computed could be considered as a function. So for an example on validation you could clearly do something like :
methods: {
validation(attr){
switch(attr) {
case 'email':
const re = /^(([^<>()\[\]\.,;:\s#\"]+(\.[^<>()\[\]\.,;:\s#\"]+)*)|(\".+\"))#(([^<>()[\]\.,;:\s#\"]+\.)+[^<>()[\]\.,;:\s#\"]{2,})$/i;
return re.test(this.form.email);
case 'password':
return this.form.password.length > 4
}
},
...
}
Which you'll be using like :
<b-form-input
id="email"
v-model="form.email"
type="email"
:state="validation('email')"
required
placeholder="Enter email"
></b-form-input>
Just keep in mind that you will still miss the caching specific to computed.

Yes methods are there for using params. Like answers stated above, in your example it's best to use methods since execution is very light.
Only for reference, in a situation where the method is complex and cost is high, you can cache the results like so:
data() {
return {
fullNameCache:{}
};
}
methods: {
fullName(salut) {
if (!this.fullNameCache[salut]) {
this.fullNameCache[salut] = salut + ' ' + this.firstName + ' ' + this.lastName;
}
return this.fullNameCache[salut];
}
}
note: When using this, watchout for memory if dealing with thousands

You need to be careful with the vue/no-side-effects-in-computed-properties ESlint rule and not making any inside of computed.
Meanwhile, if you're looking towards having a memoization kind of approach, you can give a read to this article or useMemoize from Vueuse.
Or even v-memo since Vue3.2.

I don't see the an answer for the Vue3 and/or using composition API, so here is my bit (,because I always forget how to do that).
You can make computed to accept arguments by wrapping it in another function, like this:
const firstName = ref("John");
const lastName = ref("Doe");
const fullName = (salut: string) =>
computed(() =>
`${salut} ${firstName.value} ${lastName.value}`);
If you want to use Pinia (Passing arguments to getters) for state management, you can use this computed as a getter:
// In component's setup function
const store = useStore();
const fullName = store.fullName("Hello"); // ComputedRef<string>
console.log(fullName) // "Hello John Doe"

I didn't see a clear Vue 3 example so I am adding one from an app I worked on. You call a method first that then returns a computed value. So the method will be called when Vue re-renders but then the cached computed value is returned and only executed if the reactive input variables change.
<script setup>
import { computed, ref } from 'vue'
const itemOne = ref(1);
const itemTwo = ref(2);
const getItemDoubled: (key) => {
return computed(()=> item[key].value * 2);
}
</script>
<template>
<p>get dynamic name computed value: {{ getItemDoubled('One') }}
<p>get dynamic name computed value: {{ getItemDoubled('Two') }}
</template

Related

Property in Data is undefined inside methods() method

I'm brand new to Vue so trying to understand the basics so far. I'm using Vue 3.
My intention is to:
Create a select with the items in the array as options, this works.
Once the button is clicked, store the value of the select, this also works.
Push an object using the value of the select as an object's key's value
It's at this final stage that the error occurs, specifically the line getGeneMutationData: () => this.queryConstraints.push({
Error:
Uncaught TypeError: Cannot read properties of undefined (reading 'queryConstraints')
at Proxy.getGeneMutationData (Query.vue?12c2:46:14)
at eval (runtime-dom.esm-bundler.js?3191:380:1)
at callWithErrorHandling (runtime-core.esm-bundler.js?a261:155:1)
at callWithAsyncErrorHandling (runtime-core.esm-bundler.js?a261:164:1)
at callWithAsyncErrorHandling (runtime-core.esm-bundler.js?a261:174:1)
at HTMLButtonElement.invoker (runtime-dom.esm-bundler.js?3191:366:39)
I'm not sure why this isn't working, based on the documentation I'm not sure what's different in the approaches:
data() {
return { count: 4 }
},
methods: {
increment() {
// `this` will refer to the component instance
this.count++
}
}
Here's a minimised of my code:
<template>
<select v-model="selectedGeneMutation">
<option v-for="geneMutation in geneMutations" :key="geneMutation">{{geneMutation}}</option>
</select>
<input type="button" #click="getGeneMutationData">Get results</input>
</template>
<script>
export default {
name: 'Home',
data: () => ({
geneMutations: ['ACTC',
'MYBPC3'
],
queryConstraints: [],
selectedGeneMutation: ''
}),
setup() {},
methods: {
getGeneMutationData: () => this.queryConstraints.push({
fieldPath: this.selectedGeneMutation,
opStr: '==',
value: true
})
}
};
</script>
Any help as to why I can't access the properties in 'data' would really be appreciated
You are using an Arrow Function. It's limited and doesn't have its own binding to this, that's why your method fails. The documentation you're referring to is using a traditional function expression.
So in your case try this:
getGeneMutationData: function () {
this.queryConstraints.push({
fieldPath: this.selectedGeneMutation,
opStr: "==",
value: true,
});
EDIT: Actually Nikola Pavicevic's answer is correct. Yes, using a traditional function expression solves your problem, however, it seems like mixing up composition and options API (or rather not understanding the difference between the two) is what caused the issue in the first place. In Vue 3, when you use composition API you're not using this. The setup() method returns an object, and all of its properties are exposed to the rest of the component.
You should not mix composition and options API (this is not the same, also, there is no methods in composition API), try like following snippet (composition API) or you can move your methods to options API (remove setup function):
const { ref } = Vue
const App = {
setup() {
const geneMutations = ref(['ACTC', 'MYBPC3'])
let queryConstraints = ref([])
const selectedGeneMutation = ref('')
const getGeneMutationData = () => {
queryConstraints.value.push({
fieldPath: selectedGeneMutation.value,
opStr: '==',
value: true
})
}
return {
geneMutations, queryConstraints, selectedGeneMutation, getGeneMutationData
};
}
}
Vue.createApp(App)
.mount('#app')
<script src="https://unpkg.com/vue#3.2.29/dist/vue.global.prod.js"></script>
<div id="app">
<select v-model="selectedGeneMutation">
<option v-for="geneMutation in geneMutations" :key="geneMutation">{{geneMutation}}</option>
</select>
<input type="button" #click="getGeneMutationData" value="Get results" />
<h3>{{ queryConstraints }}</h3>
</div>

Why won't my template update with it being bound to a computed property?

I am facing an issue where I have some template HTML in a component that relies on the computed getter of a Vuex method. As you can see in the template, I am simply trying to show the output of the computed property in a <p> tag with {{ getNumSets }}.
As I update the state with the UPDATE_EXERCISE_SETS mutation, I can see in the Vue devtools that the state is updated correctly, but the change is not reflected in the <p> {{ getNumSets }} </p> portion.
Template HTML:
<template>
...
<v-text-field
v-model="getNumSets"
placeholder="S"
type="number"
outlined
dense
></v-text-field>
<p>{{ getNumSets }}</p>
...
</template>
Component Logic:
<script>
...
computed: {
getNumSets: {
get() {
var numSets = this.$store.getters['designer/getNumSetsForExercise']({id: this.id, parent: this.parent})
return numSets
},
set(value) { // This correctly updates the state as seen in the Vue DevTools
this.$store.commit('designer/UPDATE_EXERCISE_SETS', {
id: this.exerciseId,
parentName: this.parent,
numSets: parseInt(value),
date: this.date
})
}
}
...
</script>
Vuex Store Logic:
...
state: {
designerBucket: []
},
getters: {
getNumSetsForExercise: (state) => (payload) => {
var numSets = 0
for (var i = 0; i < state.designerBucket.length; i++) {
if (state.designerBucket[i].id == payload.id) {
numSets = state.designerBucket[i].numSets
}
}
return numSets
}
},
mutations: {
UPDATE_EXERCISE_SETS(state, payload) {
state.designerBucket.forEach(exercise => {
if (exercise.id == payload.id) {
exercise.numSets = payload.numSets
}
})
}
}
Any insight is very appreciated!
P.S. I have also tried using a for (var i=0...) loop, looping over the indices and then using Vue.set() to set the value. This did update the value in the store as well, but the computed property is still not updating the template.
This turned into a bit of a long-winded answer, but bear with me.
Here's my hunch: since you're returning a function from your Vuex getter, Vue isn't updating your computed property on state changes because the function never changes, even if the value returned from it would. This is foiling the caching mechanism for computed properties.
Reactivity for Arrow Function Getters
One of the things to keep in mind when creating a getter like this, where you return an arrow function:
getNumSetsForExercise: (state) => (payload) => {
var numSets = 0
for (var i = 0; i < state.designerBucket.length; i++) {
if (state.designerBucket[i].id == payload.id) {
numSets = state.designerBucket[i].numSets
}
}
return numSets
}
...is that you're no longer returning actual state data from your getter.
This is great when you're using it to pull something from state that depends on data that's local to your component, because we don't need Vue to detect a change, we just need the function to access current state, which it does fine.
BUT, it may also lead to the trap of thinking that updating state should update the getter, when it actually doesn't. This is really only important when we try to use this getter in a computed property like you have in the example, due to how computed properties track their dependencies and cache data.
Computed Caching and Dependency Detection
In Vue, computed properties are smarter than they first seem. They cache their results, and they register and track the reactive values they depend on to know when to invalidate that cache.
As soon as Vue calculates the value of a computed property, it stores it internally, so that if you call the property again without changing dependencies, the property can return the cached value instead of recalculating.
The key here for your case is the dependency detection– your getter has three dependencies that Vue detects:
get() {
var numSets = this.$store.getters['designer/getNumSetsForExercise']({id: this.id, parent: this.parent})
return numSets
},
The getter: this.$store.getters['designer/getNumSetsForExercise']
this.id
this.parent
None of these values change when <v-text-field> calls your setter.
This means that Vue isn't detecting any dependency changes, and it's returning the cached data instead of recalculating.
How to Fix it?
Usually, when you run into these sorts of dependency issues, it's because the design of the state could be improved, whether by moving more data into state, or by restructuring it in some way.
In this case, unless you absolutely need designerBucket to be an array for ordering purposes, I'd suggest making it an object instead, where each set is stored by id. This would simplify the implementation by removing loops, and remove the need for your getter altogether:
...
state: {
designerBucket: {}
},
mutations: {
UPDATE_EXERCISE_SETS(state, payload) {
// Need to use $set since we're adding a new property to the object
Vue.set(state.designerBucket, payload.id, payload.numSets);
}
}
Now, instead of invoking a getter, just pull designerBucket from state and access by this.id directly:
<script>
...
computed: {
getNumSets: {
get() {
return this.$store.state.designerBucket[this.id];
},
set(value) {
this.$store.commit('designer/UPDATE_EXERCISE_SETS', {
id: this.exerciseId,
parentName: this.parent,
numSets: parseInt(value),
date: this.date
});
}
}
...
</script>
This should allow Vue to detect changes correctly now, and prevent the stale cache problem from before.
Edited: First import mapGetters from 'vuex' like this on the top of the script tag.
import { mapGetters } from "vuex"
Now in your computed object, add mapGetters and pass arguments to the getter method inside the get() method like this:-
computed: {
...mapGetters('designer',['getNumSetsForExercise']),
getNumSets: {
get() {
var numSets = this.getNumSetsForExercise({id: this.id, parent: this.parent})
return numSets
},
set(value) { // This correctly updates the state as seen in the Vue DevTools
this.$store.commit('designer/UPDATE_EXERCISE_SETS', {
id: this.exerciseId,
parentName: this.parent,
numSets: parseInt(value),
date: this.date
})
}
}
And see if it works.

Why I shouldn't set data from computed?

Using vuex, I receive an object, lets suppose
user: {name: 'test'}
And in app.vue I use this.$store.getters.user
computed: {
user: function() {
let user = this.$store.getters.user
return user
}
}
While setting also data object 'this.name'
data() {
return {
name: ''
}
}
computed: {
user: function() {
let user = this.$store.getters.user
this.name = user.name
return user
}
}
But in the lint I get this error 'unexpected side effect in computed property', (the data 'name' should be used as a v-model, to be used as a update API parameter).
I know it can be ignored if you know what you're doing, and that it is triggered for setting data from computed, but why it triggers this error? and how to workaround it?
don't set value in computed. if you need to get name of computed user you must be create new computed:
user: function() {
let user = this.$store.getters.user
return user
},
name: function() {
if(this.user.name!=undefined) return this.user.name
return ''
},
and remove name from data
but if you realy need to set name you can watch user and set name
watch: {
user(newVal) {
if(newVal.name!=undefined) this.name = newVal.name
}
}
Vue has both computed getters and setters. If you define a computed property as you did above it is only a getter. A getter is meant to only "get" a value and it should be a "pure" function with no side effects so that it is easier to read, debug and test.
From the docs for the rule that triggered the linting error on your code:
It is considered a very bad practice to introduce side effects inside
computed properties. It makes the code not predictable and hard to
understand.
In your case you might want to use a computed setter for either the user or the name values so that you can use them as v-models. You could, for example, do:
computed: {
user: function () {
return this.$store.getters.user;
},
user: {
// getter
get: function () {
return this.user.name;
},
// setter
set: function (newValue) {
this.$store.commit('setUserName', newValue);
}
}
}

How to use enums (or const) in VueJS?

I feel like an idiot for having to ask about something so seemingly simple, but I'm trying to figure out how to use "enums" in VueJS. Currently, in a file called LandingPage.js I have this bit of code:
const Form = {
LOGIN: 0,
SIGN_UP: 1,
FORGOT_PASSWORD: 2,
};
function main() {
new Vue({
el: "#landing-page",
components: {
LoginForm,
WhoIsBehindSection,
WhatIsSection,
Form,
},
data () {
return {
form: Form.LOGIN,
};
},
template: `
<div>
<LoginForm v-if="form === Form.LOGIN"></LoginForm>
<WhatIsSection></WhatIsSection>
<WhoIsBehindSection></WhoIsBehindSection>
</div>
`
});
}
It is the conditional v-if="form === Form.LOGIN" that is failing with the error messages:
Property or method "Form" is not defined on the instance but referenced during render. Make sure that this property is reactive, either in the data option, or for class-based components, by initializing the property.
Cannot read property 'LOGIN' of undefined
Just so you guys know without the conditional everything is working, and if I were to put this bit in the template
<p>{{ form }}</p>
it will print 0 on the screen. Though, putting this in the template
<p>{{ Form.LOGIN }}</p>
Will not result in it printing 0 on the screen. So I just cannot for the life of me figure out why it will not accept Form.LOGIN.
The Answer
I did add it to components, but never did I think of adding it to data. Happy that it's working now. :)
data () {
return {
form: Form.LOGIN,
Form, // I had to add this bit
};
},
Thank you MarcRo 👍
If you are using Vue in Typescript, then you can use:
import { TernaryStatus } from '../enum/MyEnums';
export default class MyClass extends Vue {
myVariable: TernaryStatus = TernaryStatus.Started;
TernaryStatus: any = TernaryStatus;
}
and then in Template you can just use
<div>Status: {{ myVariable == TernaryStatus.Started ? "Started It" : "Stopped it" }}</div>
You can use https://stackoverflow.com/a/59714524/3706939.
const State = Object.freeze({ Active: 1, Inactive: 2 });
export default {
data() {
return {
State,
state: State.Active
};
},
methods: {
method() {
return state === State.Active;
}
}
}
You only have access to properties of the Vue instance in your template. Just try accessing window or any global in your template, for example.
Hence, you can access {{ form }} but not {{ Form.LOGIN }}.
A wild guess is that it has something to do with how Vue compiles, but I don't know enough about the internals to answer this.
So just keep declaring all the properties you wish to use in your template in your Vue instance (usually as data).
You can enclose enum into class. All your data, the state, the enum variants would be in one place. The same about behaviours, so you will call form.isLogin() rather than form === Form.LOGIN and form.setLogin() rather than form = Form.Login.
The class to generate enums:
class Fenum {
constructor(start, variants) {
this.state = start;
variants.forEach(value => {
const valueC = value.charAt(0).toUpperCase() + value.slice(1);
this['is' + valueC] = () => this.state === value;
this['set' + valueC] = () => this.state = value;
})
}
}
Example of usage:
function main() {
new Vue({
el: "#landing-page",
components: {
LoginForm,
WhoIsBehindSection,
WhatIsSection,
Form,
},
data () {
return {
form: new Fenum("login", ["login", "signUp", "forgotPassword"]),
};
},
template: `
<div>
<LoginForm v-if="form.isLogin()"></LoginForm>
<WhatIsSection></WhatIsSection>
<WhoIsBehindSection></WhoIsBehindSection>
</div>
`
});
}
Vue observe nested objects, so each call of a set method (from.setLogin(), form.setSignUp(), ...) will trigger updates of the component as it should be.
The generated object from this example:
You can use $options instead of $data https://vuejs.org/v2/api/#vm-options
You can use Proxy to create object which throw runtime errors if someone will read non-defined value or try to add new value - here is createEnum (and use it in data() section)
function createEnum(name,obj) {
return new Proxy(obj, {
get(target, property) {
if (property in target) return target[property];
throw new Error(`ENUM: ${name}.${property} is not defined`);
},
set: (target, fieldName, value) => {
throw new Error(`ENUM: adding new member '${fieldName}' to Enum '${name}' is not allowed.`);
}
});
}
// ---------------
// ----- TEST ----
// ---------------
const Form = createEnum('Form',{
LOGIN: 0,
SIGN_UP: 1,
FORGOT_PASSWORD: 2,
});
// enum value exists
console.log(Form.LOGIN);
// enum value not exists
try{ console.log(Form.LOGOUT) } catch(e){ console.log(e.message)}
// try to add new value
try{ Form.EXIT = 5 } catch(e){ console.log(e.message)}
for string-like Enums where values are equal to keys you can use following helper
export function createEnumArr(name='', values=[]) {
let obj = {};
values.forEach(v => obj[v]=v);
return createEnum(name,obj);
}
const Form = createEnumArr('Form',[
"LOGIN",
"SIGN_UP",
"FORGOT_PASSWORD",
]);
The easiest way!
in main.js
const enumInfo = {
SOURCE_TYPE: {
WALLET: 1,
QR: 2
}
}
Vue.prototype.enumInfo = enumInfo
index.vue
{{enumInfo}}
For 2022 and beyond you should probably be using Vue 3 and Typescript.
The easiest way to use an enum is to map it to string values and then simply return it from your setup function.
<template>
...
<div v-if="mode == DarkModes.DARK">
do something for dark mode
</div>
...
</template>
<script lang="ts">
enum DarkModes {
BRIGHT = 'bright',
DARK = 'dark',
}
export default defineComponent({
name: 'MyDarkOrBrightComponent',
setup() {
const mode = ref(DarkModes.BRIGHT);
...
return {
mode,
DarkModes, // <- PASS YOUR ENUM HERE!
}
}
});
</script>
And if you're using the new <script setup> functionality it's just as easy ... all top level imports are automatically accessible from the template (if you want to put your enum in a separate file).
I've this problem, too.
Here my solution, just put this in the first line:
<script setup>
const Form = {
LOGIN: 0,
SIGN_UP: 1,
FORGOT_PASSWORD: 2,
};
</script>

What's the correct way to pass props as initial data in Vue.js 2?

So I want to pass props to an Vue component, but I expect these props to change in future from inside that component e.g. when I update that Vue component from inside using AJAX. So they are only for initialization of component.
My cars-list Vue component element where I pass props with initial properties to single-car:
// cars-list.vue
<script>
export default {
data: function() {
return {
cars: [
{
color: 'red',
maxSpeed: 200,
},
{
color: 'blue',
maxSpeed: 195,
},
]
}
},
}
</script>
<template>
<div>
<template v-for="car in cars">
<single-car :initial-properties="car"></single-car>
</template>
</div>
</template>
The way I do it right now it that inside my single-car component I'm assigning this.initialProperties to my this.data.properties on created() initialization hook. And it works and is reactive.
// single-car.vue
<script>
export default {
data: function() {
return {
properties: {},
}
},
created: function(){
this.data.properties = this.initialProperties;
},
}
</script>
<template>
<div>Car is in {{properties.color}} and has a max speed of {{properties.maxSpeed}}</div>
</template>
But my problem with that is that I don't know if that's a correct way to do it? Won't it cause me some troubles along the road? Or is there a better way to do it?
Thanks to this https://github.com/vuejs/vuejs.org/pull/567 I know the answer now.
Method 1
Pass initial prop directly to the data. Like the example in updated docs:
props: ['initialCounter'],
data: function () {
return {
counter: this.initialCounter
}
}
But have in mind if the passed prop is an object or array that is used in the parent component state any modification to that prop will result in the change in that parent component state.
Warning: this method is not recommended. It will make your components unpredictable. If you need to set parent data from child components either use state management like Vuex or use "v-model". https://v2.vuejs.org/v2/guide/components.html#Using-v-model-on-Components
Method 2
If your initial prop is an object or array and if you don't want changes in children state propagate to parent state then just use e.g. Vue.util.extend [1] to make a copy of the props instead pointing it directly to children data, like this:
props: ['initialCounter'],
data: function () {
return {
counter: Vue.util.extend({}, this.initialCounter)
}
}
Method 3
You can also use spread operator to clone the props. More details in the Igor answer: https://stackoverflow.com/a/51911118/3143704
But have in mind that spread operators are not supported in older browsers and for better compatibility you'll need to transpile the code e.g. using babel.
Footnotes
[1] Have in mind this is an internal Vue utility and it may change with new versions. You might want to use other methods to copy that prop, see How do I correctly clone a JavaScript object?.
My fiddle where I was testing it:
https://jsfiddle.net/sm4kx7p9/3/
In companion to #dominik-serafin's answer:
In case you are passing an object, you can easily clone it using spread operator(ES6 Syntax):
props: {
record: {
type: Object,
required: true
}
},
data () { // opt. 1
return {
recordLocal: {...this.record}
}
},
computed: { // opt. 2
recordLocal () {
return {...this.record}
}
},
But the most important is to remember to use opt. 2 in case you are passing a computed value, or more than that an asynchronous value. Otherwise the local value will not update.
Demo:
Vue.component('card', {
template: '#app2',
props: {
test1: null,
test2: null
},
data () { // opt. 1
return {
test1AsData: {...this.test1}
}
},
computed: { // opt. 2
test2AsComputed () {
return {...this.test2}
}
}
})
new Vue({
el: "#app1",
data () {
return {
test1: {1: 'will not update'},
test2: {2: 'will update after 1 second'}
}
},
mounted () {
setTimeout(() => {
this.test1 = {1: 'updated!'}
this.test2 = {2: 'updated!'}
}, 1000)
}
})
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.17/dist/vue.js"></script>
<div id="app1">
<card :test1="test1" :test2="test2"></card>
</div>
<template id="app2">
<div>
test1 as data: {{test1AsData}}
<hr />
test2 as computed: {{test2AsComputed}}
</div>
</template>
https://jsfiddle.net/nomikos3/eywraw8t/281070/
I believe you are doing it right because it is what's stated in the docs.
Define a local data property that uses the prop’s initial value as its initial value
https://vuejs.org/guide/components.html#One-Way-Data-Flow
Second or third time I run into that problem coming back to an old vue project.
Not sure why it is so complicated in vue, but it can we done via watch:
export default {
props: ["username"],
data () {
return {
usernameForLabel: "",
}
},
watch: {
username: {
immediate: true,
handler (newVal, oldVal) {
this.usernameForLabel = newVal;
}
},
},
Just as another approach, I did it through watchers in the child component.
This way is useful, specially when you're passing an asynchronous value, and in your child component you want to bind the passed value to v-model.
Also, to make it reactive, I emit the local value to the parent in another watcher.
Example:
data() {
return {
properties: {},
};
},
props: {
initial-properties: {
type: Object,
default: {},
},
},
watch: {
initial-properties: function(newVal) {
this.properties = {...newVal};
},
properties: function(newVal) {
this.$emit('propertiesUpdated', newVal);
},
},
This way I have more control and also less unexpected behaviour. For example, when props that passed by the parent is asynchronous, it may not be available at the time of created or mounted lifecycle. So you can use computed property as #Igor-Parra mentioned, or watch the prop and then emit it.
Following up on Cindy's comment on another answer:
Be carful. The spread operator only shallow clones, so for objects
that contain objects or arrays you will still copy pointers instead of
getting a new copy.
Indeed this is the case. Changes within objects inside arrays will still propagate to your components even when a spread operator is employed.
Here was my solution (using Composition API):
setup() {
properties = ref([])
onMounted(() => {
properties.value = props.initialProperties.map((obj) => ({ ...obj }));
})
}
This worked to set the values and prevent them from getting changed, even if the data was changed in the parent component.

Categories

Resources