Vue 3 - Vue.delete alternative - vue.js

What's the alternative of Vue.delete in the new Reactivity API of Vue 3?

Vue.delete and Vue.set aren't needed in Vue 3. With the new reactivity system using proxies, Vue is able to detect all changes to reactive data.
You can use JavaScript's delete operator:
delete obj[key]
Here's a Vue 3 demo removing and adding object properties with vanilla JavaScript:
const { createApp, reactive } = Vue;
const app = createApp({
setup() {
const obj = reactive({ a: 1, b: 2, c: 3 })
return { obj }
}
});
app.mount("#app");
<div id="app">
Object: {{ obj }}
<hr>
<template v-for="(item, key) in obj">
<button #click="delete obj[key]">Delete key {{ key }}</button>
</template>
<button #click="obj['z'] = 'Added'">Add key z</button>
</div>
<script src="https://unpkg.com/vue#next"></script>

Related

How do Template Refs work inside a constant?

Question about Template Refs in vue
I'm taking a look at the vue documentation about "refs" and in the part where it explains about ref inside a v-for it gives the following example:
<script setup>
import { ref, onMounted } from 'vue'
const list = ref([
/* ... */
])
const itemRefs = ref([])
onMounted(() => console.log(itemRefs.value))
</script>
<template>
<ul>
<li v-for="item in list" ref="itemRefs">
{{ item }}
</li>
</ul>
</template>
I can understand its use in
const itemRefs = ref([])
but I couldn't understand why the ref is also applied in
const list = ref([
/* ... */
])
In a sandbox it is possible to remove the ref from the "list constant" without harming the function, so what would be the real application inside this constant?
<script setup>
import { ref, onMounted } from 'vue'
// const with ref
const list = ref([1, 2, 3])
const itemRefs = ref([])
onMounted(() => {
alert(itemRefs.value.map(i => i.textContent))
})
</script>
<template>
<ul>
<li v-for="item in list" ref="itemRefs">
{{ item }}
</li>
</ul>
</template>
<script setup>
import { ref, onMounted } from 'vue'
// const without ref
const list = ([1, 2, 3])
const itemRefs = ref([])
onMounted(() => {
alert(itemRefs.value.map(i => i.textContent))
})
</script>
<template>
<ul>
<li v-for="item in list" ref="itemRefs">
{{ item }}
</li>
</ul>
</template>
Using the ref turns list into reactive variable. Whenever new item is appended or it is otherwise mutated, the other functions or template parts watching it get updated. In your example without ref, when you append a new item to the list, it won't be automatically rendered in the template.
I can understand your confusion and I assume you probably come from vue2 world. I can't recommend enough reading vue3 docs about reactivity.
These are two different types of refs:
itemRefs is an array of template ref. It's a reference to an html element / vue component from your component template. It's associated to the ref attribute on the element.
list is a "regular" ref. It adds reactivity over the array of values. Without it, Vue won't react to the value changes and won't update the rendered component.

How to use v-model using props in v-for in Vue 3

I want to use v-model using props in v-for. I used v-model with props before using computed variables, but don't know how to do in v-for.
This is my code:
<script setup>
const fieldValue = computed({
get() {
return props.modelValue;
},
set(value) {
emits('update:modelValue', value);
},
});
</script>
<template>
<Mentionable
v-for="(field, index) in props.data.placeholder"
:key="index"
:keys="['#']"
:items="props.data.items"
>
<textarea
v-model="fieldValue"
:disabled="!props.data.loaded"
:placeholder="field.placeholder"
:maxlength="props.maxLength"
:required="props.required"
/>
</Mentionable>
</template>
Note: Mentionable is the component from vue-mention library.
Use model-value and update:model-value event instend of v-model.
If the component does not provide model-value and update:model-value event, there must be prop and event you can do the same.
A example, AInput from Ant Design Vue provides value prop and change event.
<script setup name="Demo">
const {ref} from 'vue'
const arrayInputs = ref(['1', 'test', '3'])
function change(i, event) {
arrayInputs.value[i] = event.target.value
}
</script>
<template>
<div class="vue-component">
<h2>ant-design-vue</h2>
<template v-for="(item, index) in arrayInputs" :key="index">
<!-- the index is important -->
<AInput :value="item" #change="event => change(index, event)" />
</template>
<div>{{ arrayInputs }}</div>
</div>
</template>

How can I reassign and repass props data with keeping its reactivity?

<template>
<p>
<input type="text" v-model="appInput">
{{ appInput }}
</p>
<ParentComponent :appInput='appInput' :appObject='appObject'/>
</template>
<script setup>
import ParentComponent from './components/ParentComponent.vue'
import { ref } from 'vue'
const appInput = ref('')
const appObject = ref({
text1: 'text1',
text2: 'text2'
})
</script>
<!-- ------------------------------------- -->
<template>
<!-- {{ appInput }} -->
{{ props.appInput }}
<!-- {{ appObject }} -->
{{ props.appObject.text1 }}
<ChildComponentVue :appInput='appInput'/>
</template>
<script setup>
import { defineProps } from 'vue';
import ChildComponentVue from './ChildComponent.vue';
const props = defineProps(['appInput', 'appObject'])
// const appInput = props.appInput
// const appObject = props.appObject
</script>
Using Vue 3, I want to reactively render text from App.vue on ParentComponent.vue and ChildComponent.vue by passing data from App to Parent, and Parent to child successively.
But when I reassign props data on ParentComponent for use it conveniently, it lose reactivity.
I tried repack it with ref() on ParentComponent, but as Vue3 Official Document says, it doesn't work.
But it is too dirty to use passed data without reassign in template (like {{ props.appObject.text1 }}) and too hard to repass not entire but just some part of passed data.
and here are my questions
Are there some ways to use and repass passed data more concisely without losing its reactivity?
What is the convension on Vue3 to deal with this kind of problem happen. Just using Vuex?

vuejs 3 data options property no more reactive with external object

I have an app that shares an external module with other applications by sharing a global javascript object.
One of these apps is developed with vue 2 and when the global object is updated in the external module, the option data property of vue 2 is updated perfectly while in vue 3 it is not. I also tried with the new reactive property but nothing to do, is it a bug?
Not being able to make any changes to the external module because it is shared with other apps, how can I make it work in vue 3?
Here are some test links:
Vue 2 share external object
<script src="https://unpkg.com/vue"></script>
<script>
var EXTERNAL_OBJECT={
name:"Bob",
list:[{name:"Ivan"}]
}
function change_object(){
EXTERNAL_OBJECT.name+="+++"
EXTERNAL_OBJECT.list.push({name:"Carl"})
}
</script>
<button onClick="change_object()">change external object</button>
<div id="app">
<div>
{{share.name}}
</div>
<div v-for="item in share.list">
{{item.name}}
</div>
</div>
<script>
new Vue({
el: '#app',
data: {
share:EXTERNAL_OBJECT
}
})
</script>
Vue 3 share external object
<script src="https://unpkg.com/vue#3.2.4/dist/vue.global.js"></script>
<script>
var EXTERNAL_OBJECT={
name:"Bob",
list:[{name:"Ivan"}]
}
function change_object(){
EXTERNAL_OBJECT.name+="+++"
EXTERNAL_OBJECT.list.push({name:"Carl"})
}
</script>
<button onClick="change_object()">change external object</button>
<div id="app">
<div>
{{share.name}}
</div>
<div v-for="item in share.list">
{{item.name}}
</div>
</div>
<script>
const app = Vue.createApp({
data () {
return {
share:EXTERNAL_OBJECT
}
}
});
app.mount('#app')
</script>
Vue 3 share external object with reactive property
<script src="https://unpkg.com/vue#3.2.4/dist/vue.global.js"></script>
<script>
var EXTERNAL_OBJECT={
name:"Bob",
list:[{name:"Ivan"}]
}
function change_object(){
EXTERNAL_OBJECT.name+="+++"
EXTERNAL_OBJECT.list.push({name:"Carl"})
}
</script>
<button onClick="change_object()">change external object</button>
<div id="app">
<div>
{{share.name}}
</div>
<div v-for="item in share.list">
{{item.name}}
</div>
</div>
<script>
const { createApp, reactive } = Vue
const app = createApp({
setup(){
let share = reactive(EXTERNAL_OBJECT)
return {
share
}
},
data () {
return {
msg:"reactive test"
}
}
});
app.mount('#app')
</script>
thanks
It is a bit hard to read ... I just look at the Vue3 Example.
How many file are you showing?
Cant write your EXTERNAL_OBJECT directly in the reactive property? Like:
const EXTERNAL_OBJECT = reactive({ name:"Bob",
list:[{name:"Ivan"}] });

Passing props to Vue root instance via attributes on element the app is mounted on

I am terribly new to Vue, so forgive me if my terminology is off. I have a .NET Core MVC project with small, separate vue pages. On my current page, I return a view from the controller that just has:
#model long;
<div id="faq-category" v-bind:faqCategoryId="#Model"></div>
#section Scripts {
<script src="~/scripts/js/faqCategory.js"></script>
}
Where I send in the id of the item this page will go grab and create the edit form for. faqCategory.js is the compiled vue app. I need to pass in the long parameter to the vue app on initialization, so it can go fetch the full object. I mount it with a main.ts like:
import { createApp } from 'vue'
import FaqCategoryPage from './FaqCategoryPage.vue'
createApp(FaqCategoryPage)
.mount('#faq-category');
How can I get my faqCategoryId into my vue app to kick off the initialization and load the object? My v-bind attempt seems to not work - I have a #Prop(Number) readonly faqCategoryId: number = 0; on the vue component, but it is always 0.
My FaqCategoryPAge.vue script is simply:
<script lang="ts">
import { Options, Vue } from "vue-class-component";
import { Prop } from 'vue-property-decorator'
import Card from "#/Card.vue";
import axios from "axios";
import FaqCategory from "../shared/FaqCategory";
#Options({
components: {
Card,
},
})
export default class FaqCategoryPage extends Vue {
#Prop(Number) readonly faqCategoryId: number = 0;
mounted() {
console.log(this.faqCategoryId);
}
}
</script>
It seems passing props to root instance vie attributes placed on element the app is mounting on is not supported
You can solve it using data- attributes easily
Vue 2
const mountEl = document.querySelector("#app");
new Vue({
propsData: { ...mountEl.dataset },
props: ["message"]
}).$mount("#app");
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app" data-message="Hello from HTML">
{{ message }}
</div>
Vue 3
const mountEl = document.querySelector("#app");
Vue.createApp({
props: ["message"]
}, { ...mountEl.dataset }).mount("#app");
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/3.0.0/vue.global.js"></script>
<div id="app" data-message="Hello from HTML">
{{ message }}
</div>
Biggest disadvantage of this is that everything taken from data- attributes is a string so if your component expects something else (Number, Boolean etc) you need to make conversion yourself.
One more option of course is pushing your component one level down. As long as you use v-bind (:counter), proper JS type is passed into the component:
Vue.createApp({
components: {
MyComponent: {
props: {
message: String,
counter: Number
},
template: '<div> {{ message }} (counter: {{ counter }}) </div>'
}
},
}).mount("#app");
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/3.0.0/vue.global.js"></script>
<div id="app">
<my-component :message="'Hello from HTML'" :counter="10" />
</div>
Just an idea (not a real problem)
Not really sure but it can be a problem with Props casing
HTML attribute names are case-insensitive, so browsers will interpret any uppercase characters as lowercase. That means when you're using in-DOM templates, camelCased prop names need to use their kebab-cased (hyphen-delimited) equivalents
Try to change your MVC view into this:
<div id="faq-category" v-bind:faq-category-id="#Model"></div>
Further to Michal LevĂ˝'s answer regarding Vue 3, you can also implement that pattern with a Single File Component:
app.html
<div id="app" data-message="My Message"/>
app.js
import { createApp } from 'vue';
import MyComponent from './my-component.vue';
const mountEl = document.querySelector("#app");
Vue.createApp(MyComponent, { ...mountEl.dataset }).mount("#app");
my-component.vue
<template>
{{ message }}
</template>
<script>
export default {
props: {
message: String
}
};
</script>
Or you could even grab data from anywhere on the parent HTML page, eg:
app.html
<h1>My Message</h1>
<div id="app"/>
app.js
import { createApp } from 'vue';
import MyComponent from './my-component.vue';
const message = document.querySelector('h1').innerText;
Vue.createApp(MyComponent, { message }).mount("#app");
my-component.vue
<template>
{{ message }}
</template>
<script>
export default {
props: {
message: String
}
};
</script>
To answer TheStoryCoder's question: you would need to use a data prop. My answers above demonstrate how to pass a value from the parent DOM to the Vue app when it is mounted. If you wanted to then change the value of message after it was mounted, you would need to do something like this (I've called the data prop myMessage for clarity, but you could also just use the same prop name message):
<template>
{{ myMessage }}
<button #click="myMessage = 'foo'">Foo me</button>
</template>
<script>
export default {
props: {
message: String
},
data() {
return {
myMessage: this.message
}
}
};
</script>
So I'm not at all familiar with .NET and what model does, but Vue will treat the DOM element as a placeholder only and it does not extend to it the same functionality as the components within the app have.
so v-bind is not going to work, even without the value being reactive, the option is not there to do it.
you could try a hack to access the value and assign to a data such as...
const app = Vue.createApp({
data(){
return {
faqCategoryId: null
}
},
mounted() {
const props = ["faqCategoryId"]
const el = this.$el.parentElement;
props.forEach((key) => {
const val = el.getAttribute(key);
if(val !== null) this[key] = (val);
})
}
})
app.mount('#app')
<script src="https://unpkg.com/vue#3.0.0-rc.11/dist/vue.global.prod.js"></script>
<div id="app" faqCategoryId="12">
<h1>Faq Category Id: {{faqCategoryId}}</h1>
</div>
where you get the value from the html dom element, and assign to a data. The reason I'm suggesting data instead of props is that props are setup to be write only, so you wouldn't be able to override them, so instead I've used a variable props to define the props to look for in the dom element.
Another option
is to use inject/provide
it's easier to just use js to provide the variable, but assuming you want to use this in an mvc framework, so that it is managed through the view only. In addition, you can make it simpler by picking the exact attributes you want to pass to the application, but this provides a better "framework" for reuse.
const mount = ($el) => {
const app = Vue.createApp({
inject: {
faqCategoryId: {
default: 'optional'
},
},
})
const el = document.querySelector($el)
Object.keys(app._component.inject).forEach(key => {
if (el.getAttribute(key) !== null) {
app.provide(key, el.getAttribute(key))
}
})
app.mount('#app')
}
mount('#app')
<script src="https://unpkg.com/vue#3.0.0-rc.11/dist/vue.global.prod.js"></script>
<div id="app" faqCategoryId="66">
<h1>Faq Category Id: {{faqCategoryId}}</h1>
</div>
As i tried in the following example
https://codepen.io/boussadjra/pen/vYGvXvq
you could do :
mounted() {
console.log(this.$el.parentElement.getAttribute("faqCategoryId"));
}
All other answers might be valid, but for Vue 3 the simple way is here:
import {createApp} from 'vue'
import rootComponent from './app.vue'
let rootProps = {};
createApp(rootComponent, rootProps)
.mount('#somewhere')