Why is v-model not working in my Vue component? - vuejs3

I have the following code...
const Blue = {
props: ['value'],
computed:{
onEmit(){
console.log("Calling");
return `${this.value} up`
}
},
template: `
<button #click="$emit('input', onEmit)">Push me</button>
`
}
export {Blue};
I try to use it like...
const InnerComponent = {
data(){
return {
blue: "Going"
}
},
template: `
<div>
<h1>{{blue}}</h1>
<blue v-model="blue"></blue>
</div>
`
}
export {InnerComponent}
But when I click the button the <h1> doesn't change. What am I missing? How do I use v-model?
This works fine but I was under the impression I could replace the :value and #input with v-model
<blue :value="blue" #input="doInput"></blue>

I got it looking at this...
const Blue = {
props: ['modelValue'],
emits: ['update:modelValue'],
computed:{
onEmit(){
return `${this.modelValue} up`
}
},
template: `
<button #click="$emit('update:modelValue', onEmit)">Push me {{this.modelValue}}</button>
`
}
export {Blue};

Related

Vue3 prop stays undefined even when watcher sees new and old values

I'm trying to pass data from a component through the parent to another child components props but the prop stays undefined in vue tools.
I tried having another variable stored in data and setting its value in the watcher for the prop but it stays undefined aswell.
Here's a snippet of what I'm trying to do.
App.vue
<template>
<NameDisplayer :name="name" />
<NameChanger #update="(n) => updateDisplayer(n)"/>
</template>
<script>
import NameDisplayer from './NameDisplayer.vue';
import NameChanger from './NameChanger.vue';
export default {
components:{
NameDisplayer,
NameChanger
},
data: function(){
return{
name: ""
}
},
methods: {
updateDisplayer(newVal){
this.name = newVal;
}
}
}
</script>
NameDisplayer.vue
<template>
<span>{{ name }}</span> //Name is always empty
</template>
<script>
export default:{
name:"NameDisplayer",
props:{
name: String
},
watch:{
name(newV, oldV){
console.log("Watcher: ", newV, oldV); //This prints out the correct values
}
}
}
</script>
NameChanger.vue
<template>
<span></span>
<input type="text" v-model="name" />
<button #click="updateName()">Update</button>
</template>
<script>
export default{
name:"NameChanger",
data: function(){
return{
name: ""
}
},
methods:{
updateName(){
this.$emit('update', this.name);
}
}
}
</script>

Vue 3 component data binding doesn't work

In the following example, {{test}} doesn't get updated according to the input of the component. What am I doing wrong?
<html>
<body>
<Component v-model="test"></Component>
{{test}}
<script type="module">
import {createApp} from './node_modules/vue/dist/vue.esm-browser.prod.js';
const Component = {
props: {
modelValue: String,
},
emits: [
'update:modelValue',
],
template: `<input #keyup="updateValue">`,
methods: {
updateValue(event) {
this.$emit('update:modelValue', event.target.value);
},
},
};
const app = createApp({});
app.component('Component', Component);
app.mount('body');
</script>
</body>
</html>
You forget to declare test variable in data options.
const app = createApp({
data: () => {
return {
test: ''
}
}
});

How to make an object passed as a prop in Vue 3 reactive?

I need to reactively change my component when a field in passed object changes.
<template>
<my-component :prop="prop" />
</template>
<script>
export default {
data() {
return {
prop: {
key: 'value',
flag: true
}
}
}
}
</script>
mycomponent.vue
<template>
<div v-if="flag">Yay, it's a flag!</div>
<div v-else>I am very sad rn</div>
</template>
<script>
export default {
props: {
prop: Object
},
setup(props) {
const prop = ref(props, 'prop')
const flag = // *
return { flag }
}
}
</script>
Don't know what to do here, prop.flag, prop.value.flag doesn't work.
I also tried something like const flag = ref(prop, 'flag') and then flag.value, or even const flag = req(prop.value, 'flag'), but no luck.
Props are accessible and reactive in components should you declare them. Since you haven't, they won't be available.
For example, this is all you need:
<template>
<div v-if="prop.flag">Yay, it's a flag!</div>
<div v-else>I am very sad rn</div>
</template>
<script>
export default {
props: {
prop: Object
}
}
</script>
Just use toRef or toRefs
<template>
<div v-if="flag">Yay, it's a flag!</div>
<div v-else>I am very sad rn</div>
</template>
<script>
import { toRefs, toRef } from 'vue';
export default {
props: {
prop: Object
},
setup(props) {
const { prop } = toRefs(props);
//alternative
const prop = toRef(props, 'prop');
const flag = // *
return { flag }
}
}
</script>

Dynamic component in Vue3 Composition API

A simple working example of a Vue2 dynamic component
<template>
<div>
<h1>O_o</h1>
<component :is="name"/>
<button #click="onClick">Click me !</button>
</div>
</template>
<script>
export default {
data: () => ({
isShow: false
}),
computed: {
name() {
return this.isShow ? () => import('./DynamicComponent') : '';
}
},
methods: {
onClick() {
this.isShow = true;
}
},
}
</script>
Everything works, everything is great. I started trying how it would work with the Composition API.
<template>
<div>
<h1>O_o</h1>
<component :is="state.name"/>
<button #click="onClick">Click me !</button>
</div>
</template>
<script>
import {ref, reactive, computed} from 'vue'
export default {
setup() {
const state = reactive({
name: computed(() => isShow ? import('./DynamicComponent.vue') : '')
});
const isShow = ref(false);
const onClick = () => {
isShow.value = true;
}
return {
state,
onClick
}
}
}
</script>
We launch, the component does not appear on the screen, although no errors are displayed.
You can learn more about 'defineAsyncComponent' here
https://labs.thisdot.co/blog/async-components-in-vue-3
or on the official website
https://v3.vuejs.org/api/global-api.html#defineasynccomponent
import { defineAsyncComponent, defineComponent, ref, computed } from "vue"
export default defineComponent({
setup(){
const isShow = ref(false);
const name = computed (() => isShow.value ? defineAsyncComponent(() => import("./DynamicComponent.vue")): '')
const onClick = () => {
isShow.value = true;
}
}
})
Here is how you can load dynamic components in Vue 3. Example of dynamic imports from the icons collection inside /icons folder prefixed with "icon-".
BaseIcon.vue
<script>
import { defineComponent, shallowRef } from 'vue'
export default defineComponent({
props: {
name: {
type: String,
required: true
}
},
setup(props) {
// use shallowRef to remove unnecessary optimizations
const currentIcon = shallowRef('')
import(`../icons/icon-${props.name}.vue`).then(val => {
// val is a Module has default
currentIcon.value = val.default
})
return {
currentIcon
}
}
})
</script>
<template>
<svg v-if="currentIcon" width="100%" viewBox="0 0 24 24" :aria-labelledby="name">
<component :is="currentIcon" />
</svg>
</template>
You don't need to use computed or watch. But before it loads and resolved there is nothing to render, this is why v-if used.
UPD
So if you need to change components (icons in my case) by changing props use watchEffect as a wrapper around the import function.
watchEffect(() => {
import(`../icons/icon-${props.name}.vue`).then(val => {
currentIcon.value = val.default
})
})
Don't forget to import it from vue =)
The component should be added to components option then just return it name using the computed property based on the ref property isShow :
components:{
MyComponent:defineAsyncComponent(() => import("./DynamicComponent.vue"))
},
setup(){
const isShow = ref(false);
const name = computed (() => isShow.value ? 'MyComponent': '')
const onClick = () => {
isShow.value = true;
}
}
Instead of string you should provide Component
<script setup>
import Foo from './Foo.vue'
import Bar from './Bar.vue'
</script>
<template>
<component :is="Foo" />
<component :is="someCondition ? Foo : Bar" />
</template>

Vue.js v-show not working after axios

v-show is not working as I expected, I'm guessing it is because this.conversation.hidden is not set when browser is rendered since it is coming with async call. In this case, how can I make it work?
Thanks in progress! And tell me if my description is insufficientã… 
<template>
<div>
<div v-show="conversation.hidden">hidden</div>
<div v-show="!conversation.hidden">not hidden</div>
<button #click="conversation.hidden = false">Click Me!</button>
</div>
</template>
<script>
import axios from 'axios';
export default {
data() {
return {
conversation: {},
};
},
created() {
axios.get('request_something', {
}).then((response) => {
this.conversation = response.data;
this.conversation.hidden = true;
});
},
};
</script>
---------SOLVED--------------
as #thanksd mentioned in my comment, using Vue.set() will solve this problem. I should have explained more briefly with my question, but he knew what I was looking for. :) Thanks to others who answered me too.
If you add the hidden to your conversation object it will work. Vue will look for the object and if the object is undefined it won't work.
conversation: {
hidden: false
}
take a look at jsFiddle example
https://jsfiddle.net/eywraw8t/141516/
Perhaps you could try conditional rendering:
<template>
<div v-cloak>
<div v-if="conversation.hidden">Hidden</div>
<div v-else>Not Hidden</div>
<button #click="toggleConversation">Click Me!</button>
</div>
</template>
<script>
export default {
data() {
return {
conversation: {
data: [],
hidden: true
}
}
},
created() {
axios.get('/endpoint').then((response) => {
if (response.data) {
this.conversation.data = response.data
this.conversation.hidden = false
}
})
},
methods: {
toggleConversation() {
this.conversation.hidden = !this.conversation.hidden
}
}
}
</script>

Resources