I have this object:
this.object = {
one: { ... },
two: { ... },
three: { ... }
}
How can I remove, for example, the property three and make the UI rerender? I already tried to use delete but it seems that it does not change the state and the UI did not rerender.
When I use this.object = { }, it does rerender the UI. Last question, what type of object is this? Because it's hard to find an example or answer that uses this type of object.
From the Vue Reactivity Guide (which doesn't specifically tell you how to delete)
Vue cannot detect property addition or deletion
In order for these mutations to be reactive, you have to use Vue's built-in methods. You could do:
this.$delete(this.object, 'three');
OR
this.$set(this.object, 'three', undefined);
To answer your question about the object, it's an object literal.
Demo:
Vue.config.productionTip = false;
Vue.config.devtools = false
new Vue({
el: "#app",
data() {
return {
object: {
one: {},
two: {},
three: {}
}
}
},
methods: {
deleteProp() {
this.$delete(this.object, 'three');
//this.$set(this.object, 'three', undefined);
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
{{ object }}
<button #click="deleteProp">Delete property</button>
</div>
Try this in the component
<template>
<ul>
<li v-for="(obj,key) in object" #click="deleteKey(key)">{{obj}}</li>
</ul>
</template>
export default {
name: "YourComponent",
data: () => {
return {
object: {
one: {},
two: {},
three: {}
}
}
},
methods: {
deleteKey: function (key) {
this.$delete(this.object, key);
}
},
components: {Loader}
}
On clicking the listed values, it will be removed and can see the UI changing.
Just do:
this.object.three = { } // just make it empty, or
delete this.object.three // or
delete this.object['three']
Related
I'm sending from the parent component a prop: user. Now in the child component I want to make a copy of it without it changing the prop's value.
I tried doing it like this:
export default defineComponent({
props: {
apiUser: {
required: true,
type: Object
}
},
setup(props) {
const user = ref(props.apiUser);
return { user };
}
});
But then if I change a value of the user object it also changes the apiUser prop. I thought maybe using Object.assign would work but then the ref isn't reactive anymore.
In Vue 2.0 I would do it like this:
export default {
props: {
apiUser: {
required: true,
type: Object
}
},
data() {
return {
user: {}
}
},
mounted() {
this.user = this.apiUser;
// Now I can use this.user without changing this.apiUser's value.
}
};
Credits to #butttons for the comment that lead to the answer.
const user = reactive({ ...props.apiUser });
props: {
apiUser: {
required: true,
type: Object
}
},
setup(props) {
const userCopy = toRef(props, 'apiUser')
}
With the composition API we have the toRef API that allows you to create a copy from any source reactive object. Since the props object is a reactive, you use toRef() and it won't mutate your prop.
This is what you looking for: https://vuejs.org/guide/components/props.html#one-way-data-flow
Create data where you add the prop to
export default {
props: ['apiUser'],
data() {
return {
// user only uses this.apiUser as the initial value;
// it is disconnected from future prop updates.
user: this.apiUser
}
}
}
Or if you use api composition:
import {ref} from "vue";
const props = defineProps(['apiUser']);
const user = ref(props.apiUser);
You also may want to consider using computed methods (see also linked doc section from above) or v-model.
Please note that the marked solution https://stackoverflow.com/a/67820271/2311074 is not working. If you try to update user you will see a readonly error on the console. If you don't need to modify user, you may just use the prop in the first place.
As discussed in comment section, a Vue 2 method that I'm personally fond of in these cases is the following, it will basically make a roundtrip when updating a model.
Parent (apiUser) ->
Child (clone apiUser to user, make changes, emit) ->
Parent (Set changes reactively) ->
Child (Automatically receives changes, and creates new clone)
Parent
<template>
<div class="parent-root"
<child :apiUser="apiUser" #setUserData="setUserData" />
</div>
</template>
// ----------------------------------------------------
// (Obviously imports of child component etc.)
export default {
data() {
apiUser: {
id: 'e134',
age: 27
}
},
methods: {
setUserData(payload) {
this.$set(this.apiUser, 'age', payload);
}
}
}
Child
<template>
<div class="child-root"
{{ apiUser }}
</div>
</template>
// ----------------------------------------------------
// (Obviously imports of components etc.)
export default {
props: {
apiUser: {
required: true,
type: Object
}
},
data() {
user: null
},
watch: {
apiUser: {
deep: true,
handler() {
// Whatever clone method you want to use
this.user = cloneDeep(this.apiUser);
}
}
},
mounted() {
// Whatever clone method you want to use
this.user = cloneDeep(this.apiUser);
},
methods: {
// Whatever function catching the changes you want to do
setUserData(payload) {
this.$emit('setUserData', this.user);
}
}
}
Apologies for any miss types
I've seen some questions about vue.js watchers, but i didnt find a question handling my problem, so here it is:
In my SomeComponent.vue i use this code:
...
props: ['value']
,
watch: {
value(val) {
console.log(val);
}
},
In my parent vue page, that uses this component i use this, which is working:
<template>
<div>
<SomeComponent
v-model="test"
></SomeComponent>
</div>
</template>
data() {
return {
test: {}
};
},
created() {
this.test = this.DoSomething();
},
If i add another property the watcher is not triggered anymore:
<template>
<div>
<SomeComponent
v-model="test"
></SomeComponent>
</div>
</template>
data() {
return {
test: {}
};
},
created() {
this.test.Prop1 = this.DoSomething();
this.test.Prop2 = "Test";
},
EDIT:
After Behappy's Answer my Component Part looks like this now:
...
props: ["value"],
watch: {
value: {
handler(val) {
console.log(val);
},
deep: true
}
},
This is because your prop is an Object.
For deeply watching you can do this:
watch: {
value: {
handler(val) {
console.log(val);
},
deep: true
}
},
and in created DOM is not rendered yet. So prop you sent to child component is not updated yet.refer link
and as in other answers the correct way to change Object is to use this.$set:
mounted() {
this.$set(this.test, 'Prop1', this.DoSomething())
this.$set(this.test, 'Prop2', 'Test')
},
As mentioned in the docs:
Due to limitations in JavaScript, there are types of changes that Vue cannot detect. However, there are ways to circumvent them to preserve reactivity.
For Objects
Vue cannot detect property addition or deletion. Since Vue performs the getter/setter conversion process during instance initialization, a property must be present in the data object in order for Vue to convert it and make it reactive. For example:
var vm = new Vue({
data: {
a: 1
}
})
// `vm.a` is now reactive
vm.b = 2
// `vm.b` is NOT reactive
Vue does not allow dynamically adding new root-level reactive properties to an already created instance. However, it’s possible to add reactive properties to a nested object using the this.$set(object, propertyName, value) method:
In your case that would be:
created() {
this.$set(this.test, 'Prop1', this.DoSomething())
this.$set(this.test, 'Prop2', "Test")
},
Or, you can also assign a number of properties to an existing object, for example, using Object.assign() like:
this.test = Object.assign({}, this.test, { 'Prop1': 1, 'Prop2': 2 })
I just started using Vue and I have a very simple issue that I just can't get to work! I'm trying to create a mounted event that runs a method with a specific parameter inside it to alter the "show" value of an element. here is the code:
export default {
data(){
return {
one: false,
}
},
methods: {
show: function(el) {
this.el = true;
}
},
mounted(){
this.show(this.one)
}
}
I want "el" to be just a generic placeholder for whatever "data" name is passed into the method. in the future I may not only have "one" but also "two", "three" and "four". I want the "show" method to be able to take in any reference to one of these 4 options and change its value from false to true.
in the show method, I get the error "'el' is defined but never used."
the only solution I've come to is to do an if method "if this.one === el{...}" but that kind of defeats the purpose. any help would be appreciated
You can do something like this:
export default {
data(){
return {
one: false,
two: false
}
},
methods: {
doSomething(el) {
this[el] = true;
}
},
mounted(){
//Also works with vue props!
this.doSomething('one')
this.doSomething('two')
}
}
But if the function is more complex you should build a componet for that. Thats the vue way.
new Vue({
el: '#editor',
data: {
el: false,
item : ''
},
computed: {
},
methods: {
show (passedValue, item) {
this.item = item
this.el = passedValue
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<script src="https://unpkg.com/marked#0.3.6"></script>
<script src="https://unpkg.com/lodash#4.16.0"></script>
<div id="editor">
<div id="div1" v-if="el===true && item =='div-1'" class="show">
div1 displayed
</div>
<div id="div-2" v-if="el===true && item =='div-2'" class="show">
div2 displayed
</div>
<button v-on:click="show(true, 'div-1')">show div1</button>
<button v-on:click="show(false, 'div-1')">hide div1</button>
<button v-on:click="show(true, 'div-2')">show div2</button>
<button v-on:click="show(false, 'div-2')">hide div2</button>
</div>
export default {
data(){
return {
one: false,
}
},
methods: {
show(bol) {
this.one = bol;
}
},
mounted(){
this.show(this.one)
}
}
Use above code it would work.
Given that a colon indicates one-way-data-binding in VueJS2, I would like to understand why in this example, the child is able to update the array that was declared in the parent and passed to the child via prop (one-way).
https://jsfiddle.net/ecgxykrt/
<script src="https://unpkg.com/vue"></script>
<div id="app">
<span>Parent value: {{ dataTest }}</span>
<test :datatest="dataTest" />
</div>
var test = {
props: ['datatest'],
mounted: function() {
this.datatest.push(10)
},
render: function() {}
}
new Vue({
el: '#app',
components: {
'test': test
},
data: function() {
return {
dataTest: []
}
}
})
Thanks in advance!
Vue prevents you from assigning to a prop. It does not prevent you from calling a prop's methods or modifying its elements or members, any of which can change the contents of the object. None of these things changes the value of the prop itself, which is a reference to an underlying structure.
A related issue is the fact that Vue cannot detect changes to Array elements or additions/deletions of Object members.
More here.
If you wanted to, you could avoid this by creating a shallow copy and assigning it to a new data item in the child.
https://jsfiddle.net/6xxba1fz/
var test = {
props: ['test'],
data: function() {
return {
myTest: this.test.slice()
}
},
mounted: function() {
this.myTest.push(10)
},
render: function() {}
}
new Vue({
el: '#app',
components: {
'test': test
},
data: function() {
return {
dataTest: []
}
}
})
Please avoid to using the some name for key and value
:datatest="dataTest" Wrong Way
:data-test="dataTest" Better Way (use Kabab case)
HTML
<div id="app">
<span>Parent value: {{ dataTest }}</span>
<test :data-test="dataTest" />
</div>
JS
var test = {
props: {
dataTest:{
type:Number
}
},
mounted: function() {
this.datatest.push(10)
},
render: function() {}
}
new Vue({
el: '#app',
components: {
'test': test
},
data: function() {
return {
dataTest: []
}
}
})
Result:
Parent value: []
I'm trying to access a props of a component when this last triggers an event. This component is coming from http://element.eleme.io/#/en-US/component/switch.
It has several props such as name, value and so on. I would like to be able to get the name or the value fo the switch when change is triggered.
Even more, how to access any props of the switch that triggered the change event ?
I tried this but I get undefined.
<div v-for="organizer in organizers>
<el-switch #change="changeOrganizers($event.target.name, $event.target.value)" :name="organizer.name">
</el-switch>
</div>
var Main = {
data() {
return {
value1: true,
}
},
methods : {
changeSwitch(name) {
console.log(name)
}
}
};
var Ctor = Vue.extend(Main)
new Ctor().$mount('#app')
http://jsfiddle.net/2hr6y79h/2/
Thank you
Solution
<div v-for="organizer in organizers>
<el-switch #change="changeOrganizers()" :name="organizer.name">
</el-switch>
</div>
var Main = {
data() {
return {
value1: true,
}
},
methods : {
changeSwitch() {
console.log(event.currentTarget.checked);
console.log(event.currentTarget.name)
}
}
};
var Ctor = Vue.extend(Main)
new Ctor().$mount('#app')
Simply try
#change="changeSwitch"
http://jsfiddle.net/2hr6y79h/3/
That will give you the value.
The component will (probably) use
this.$emit('change', this.value)
which passes the name as the sole argument to your bound "change" event handler.
If you want the name as passed via props, I doubt it changes so just save a reference to it in your Vue instance / component, eg
data () {
return {
value1: true,
name: 'test-name'
}
}
and use
<el-switch ... :name="name"
Then you can always access it via this.name.
<div v-for="organizer in organizers>
<el-switch #change="changeOrganizers()" :name="organizer.name">
</el-switch>
</div>
var Main = {
data() {
return {
value1: true,
}
},
methods : {
changeSwitch() {
console.log(event.currentTarget.checked);
console.log(event.currentTarget.name)
}
}
};
var Ctor = Vue.extend(Main)
new Ctor().$mount('#app')