Updating prop value in child component when parent property updates Vue 3 - vue-component

I'm having troubles to update the value of a prop in a child component, the documentation says "when the parent property updates, it will flow down to the child" but that is not happening, i've made a summary of the code:
Parent's:
<script setup>
import Child from "../components/Child.vue"
var counter = 0
setInterval(() => {
counter++
console.log(counter)
}, 2000)
</script>
<template>
<Child :counter="counter"/>
</template>
Child's:
<script setup>
import { ref } from "vue"
const props = defineProps(['counter'])
const count = ref(props.counter)
</script>
<template>
<h1>{{ count }}</h1>
</template>
Anyone knows what could be wrong? I already tried to pass a ref object to the child and to put a watch function on the prop but it didn't work

The antwort from Gaetan C. is right, but not enough.
The counter data property is not reactive and changing it will not trigger updates.
var counter = 0;
should be
const counter = ref(0);
and then
counter.value++;
Here is the playground
const { createApp, ref } = Vue;
const Child = {
props: ['counter'],
template: '<h1>{{ counter }}</h1>'
}
const App = {
components: {
Child
},
setup() {
const counter = ref(0);
setInterval(() => {
counter.value++
}, 2000)
return { counter }
}
}
const app = createApp(App)
app.mount('#app')
<div id="app">
<child :counter="counter"></child>
</div>
<script src="https://unpkg.com/vue#3/dist/vue.global.js"></script>

Related

how can i render a template string like “<div>{{mydata.value_name}}</div>” in realtime with vue3

How can I render a template string like <div>{{mydata.value_name}}</div> in realtime with vue3
The data mydata I can definition dynamic
<script setup>
let dynamicCom
http.post("/someGate",(res)=>{
// when i get response ,i can initial the component dynamically
dynamicCom.template = "<div>{{mydata.value_name}}</div>"
dynamicCom.data = {
mydata:{value_name:"jim"}
}
})
</script>
<template>
<dynamicCom/>
</template>
If you only want to dynamically update some value, then Vue reactivity is enough. Just make your mydata reactive
const mydata = reactive({ value_name: "jim" })
If you want to render your component content dynamically, then you should use Vue Render Functions & JSX
Here is a simple playground to demonstrate both ways
const { createApp, h, reactive } = Vue;
const MyComponentA = {
setup() {
const mydata = reactive( {value_name: "jim"} );
return { mydata }
},
template: `
<div>
<input v-model="mydata.value_name" #update="mydata.value_name == $event.target.value" />
<div>value_name: {{mydata.value_name}}</div>
</div>`
}
const MyComponentB = {
setup() {
const mydata = reactive( {value_name: "jim"} );
return () =>
h('div', null,
[
h('input', {
value: mydata.value_name,
'onInput': (event) => mydata.value_name = event.target.value
}),
h('div', null, `value_name: ${mydata.value_name}`)
]
)
}
}
const App = { components: { MyComponentA, MyComponentB } }
const app = createApp(App)
app.mount('#app')
#app { line-height: 2; }
[v-cloak] { display: none; }
<div id="app">
<b>Vue Reactivity</b>
<my-component-a></my-component-a><hr/>
<b>Vue Reactivity & Render Functions</b>
<my-component-b></my-component-b>
</div>
<script src="https://unpkg.com/vue#3/dist/vue.global.prod.js"></script>

How to access instance in vue3 composition API lifecycle hooks

I stumbled into a totally unexpected problem while refactoring my code to composition API: there doesn't seem to be any (documented) way of accessing current instance from the lifecycle hooks.
sample code:
import { defineComponent, onMounted } from 'vue';
export default defineComponent({
setup() {
onMounted(() => {
console.log(this); // <-- will be undefined
});
},
mounted() {
console.log(this); // <-- will be the component
},
}
I've spent hours trying to find a solution to this and ultimately just used the old options API to get what I want. None of examples, tutorials or documentation - that I read - use this in the hooks.
But I find it unbelievable that only undocumented getCurrentInstance would be the way to get the current instance from the hook.
So, which doc did I miss?
UPDATE
Here is the same example with a component
const { createApp, ref, onMounted } = Vue;
const MyComponent = {
setup() {
const id = ref(Math.round(Math.random() * 100000));
const count = ref(0);
const plus = () => { count.value++; }
const minus = function() { count.value--; }
onMounted(() => {
count.value = Math.round(Math.random() * 10)
});
return {id, count, plus, minus }
},
template: `id: {{id}} <button type="button" #click="minus()">-1</button>
{{count}}
<button type="button" #click="plus()">+1</button><hr/>`
}
const App = {
components: {
MyComponent
}
}
const app = createApp(App)
app.mount('#app')
<div id="app">
<my-component v-for="i in 5" />
</div>
<script src="https://unpkg.com/vue#3/dist/vue.global.prod.js"></script>
What for do you need this in the component?
If you create your component with Composition API, then you can access all the properties directly, without using this.
Here is a very basic example:
const { createApp, ref, onMounted } = Vue;
const App = {
setup() {
const count = ref(0);
const up = () => { count.value++; }
const down = function() { count.value--; }
onMounted(() => {
count.value = 10
});
return {count, up, down }
}
}
const app = createApp(App)
app.mount('#app')
<div id="app">
<button type="button" #click="down()">-1</button>
{{count}}
<button type="button" #click="up()">+1</button>
</div>
<script src="https://unpkg.com/vue#3/dist/vue.global.prod.js"></script>

Vue 3 Composition API: Update Child components props dynamically when values update from the parent component

I am trying to update a prop value when the data from the parent component gets updated and passes through the prop. The parent value always updates but does not update or re-renders in the child component when I pass it down. It passes to the prop the first time the child component is accessed but not when the data is updated in the parent component.
Below is the parent component:
<script setup>
import { inject, watchEffect, ref } from "vue";
import ChildComponent from "#/components/ChildComponent.vue"
const { state } = inject("store");
const cart = ref(state.cart);
watchEffect(() => (cart.value = state.cart));
</script>
<template>
<ChildComponent
v-for="(item, index) in cart?.items"
:key="index"
:cartItem="item"
/>
</template>
Below is the child component (only logs on the first load, never loads again):
<script setup>
import { ref, watchEffect } from "vue";
const { cartItem } = defineProps({
cartItem: !Object
});
const item = ref(cartItem);
watchEffect(() => {
console.log(item.value)
});
</script>
I have tried using Watch in many ways but it does not detect the old or the new values. It does not log any outputs
Example child component using watch:
<script setup>
import { ref, watch } from "vue";
const { cartItem } = defineProps({
cartItem: !Object
});
const item = ref(cartItem);
watch(() => item.value, (oldValue, newValue) => {
console.log(oldValue)
console.log(newValue)
});
</script>
I ended up solving the solution by using a v-if to rerender the child component.
<script setup>
import { inject, watchEffect, ref } from "vue";
import ChildComponent from "#/components/ChildComponent.vue"
const { state } = inject("store");
const cart = ref(state.cart);
const render = ref(true);
// Checks when the cart changes from the store
watchEffect(() => {
if(cart.value) {
render.value = true
}
else {
render.value = false
}
};
</script>
<template>
<div v-if="render">
<ChildComponent
v-for="(item, index) in cart?.items"
:key="index"
:cartItem="item"
/>
</div>
</template>
I had the same issue and it was frustrating, sometimes I had to do a workaround to get what I need, but try this inside the child component:
<script>
import { ref, watch } from "vue";
export default {
props: {
cartItem: {
type: !Object,
},
},
setup(props) {
const item = ref(null);
watch(props, () => {
item.value = props.cartItem;
});
return { item }
}
</script>

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>

Resources