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

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>

Related

VueJs 3 - Reactive Render Function using Script Setup and Composition API

I have a component called x-input that works as following:
<x-input k="name" v-model="r"></x-input>
<x-input k="name" :modelValue="r"></x-input>
This works fine and it is reactive.
How do I create the same functionality using a render function with Composition API and <script setup>?
Here is what i am trying but doesn't work:
<script setup>
import { h, ref} from 'vue'
const r = ref("test")
const entity = h(resolveComponent('x-input'), {k: "name", modelValue: r }, {})
</script>
<template>
<entity />
</template>
Here entity is not reactive.
Where do you define/import your x-input component?
If you import it, then you don't need to resolve it.
<script setup>
import { h, ref, resolveComponent, } from 'vue'
import xInput from './xInput.vue'
const k = ref("name")
const r = ref("test")
const update = () => {
k.value = "name new"
r.value = "test new"
}
const entity = h('div',
[ h('p', 'Entity: '),
h(xInput, {k: k, modelValue: r }),
h('button', { onClick: () => { update() } }, 'update')
])
</script>
<template>
<entity />
</template>
Here is the working SFC Playground
You don't need to resolve your component. Just use the component itself.
h(xInput, {k: k, modelValue: r }, {}),
resolveComponent() is great when you have dynamic component names.
Check: https://stackoverflow.com/a/75405334/2487565
Here is the working playground
const { createApp, h, ref, resolveComponent } = Vue;
const xInput = {
name: 'x-input',
props: ['k', 'modelValue'],
template: '<div>k: {{k}}<br/>modelValue: {{modelValue}}<br/></div>'
}
const Entity = {
components: {
xInput
},
setup(props, { attrs, slots, emit, expose } ) {
const r = ref("test")
const k = ref("name")
const update = () => {
k.value = "name new"
r.value = "test new"
}
return () => h('div',
[
// You don't need to resolve your component
h(xInput, {k: k, modelValue: r }, {}),
h(resolveComponent('x-input'), {k: k, modelValue: r }, {}),
h('button', { onClick: () => { update() } }, 'update')
])
}
}
const app = createApp({ components: { Entity } })
app.mount('#app')
#app { line-height: 1.5; }
[v-cloak] { display: none; }
<div id="app">
<entity></entity>
</div>
<script src="https://unpkg.com/vue#3/dist/vue.global.prod.js"></script>

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

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>

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>

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