How can I make a simple copy of a reactive object with vue composition api.
I would like to do
state = form
instead of
state.var1 = form.var1
state.var2 = form.var2
Here is an example to illustrate why I have this question.
// <FormModal>
<template>
<button #click="submitHandler" >Submit</button>
</template>
<script>
import { defineComponent, reactive } from "vue"
export const defineComponent({
setup(){
const state = reactive({
var1: "var1",
var2: "var2"
})
function submitHandler(){
emit("submit", state)
}
return { submitHandler }
}
})
</script>
// <Parent>
<template>
<form-modal #submit="formSubmitHandler" />
</template>
<script>
import { defineComponent, reactive } from "vue"
export const defineComponent({
setup(){
const state = reactive({
var1: "var1",
var2: "var2"
})
function formSubmitHandler(form){
// state = form => What I would like to do!!!
state.var1 = form.var1
state.var2 = form.var2
}
return { formSubmitHandler }
}
})
</script>
You can destructure your object
function formSubmitHandler(form){
state = {...form}
}
const form = {
var1: "var1",
var2: "var2"
}
const state = {...form}
console.log(state)
Related
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>
I want to share the state between 2 components.
One have the input form, and the other displays the result.
//useForm.js
import { reactive } from "vue"
const state = reactive({
text:""
})
export default function useForm() {
const addText = () => {
state.text = state.text + "a"
}
return {
state,
addText
};
}
//Component1.vue
<template>
<button #click="addText">Add Text</button>
</template>
<script setup>
import useForm from "src/composables/useForm";
const { addText } = useForm();
</script>
//Component2.vue
<template>
{{ state.text }}
</template>
<script setup>
import useForm from "src/composables/useForm";
const { state} = useForm();
console.log(state.text)
</script>
When I click the button in Component1.vue the state in Component2.vue remains the same. How can I make it reactive? Or What I am doing wrong?
Thanks!
How can I get a reactive component that updates nested properties:
I have a pinia store defined as follows
import { defineStore } from "pinia"
export const useStore = defineStore({
id: "poc",
state: () => ({ str: "", nested: { obj: "" } }),
persist: {
enabled: true,
strategies: [{ storage: localStorage }],
},
})
and the following vue3 component
<script lang="ts">
import { ref } from "vue"
import { storeToRefs } from "pinia"
import { useStore } from "./store"
export default {
setup() {
const store = useStore()
const example = storeToRefs(store)
const mStr = ref(example.str)
const mObj = ref(example.nested.value.obj) // <--- this is where I believe the problem is
store.str = mStr.value
store.nested.obj = mObj.value
return { mObj, mStr, store }
},
}
</script>
<template>
<h1>PoC</h1>
<input v-model="mObj" placeholder="obj" />
<input v-model="mStr" placeholder="str" />
</template>
when I update the str field it works as expected, but for nested object it doesn't. My suspicion is that I lose reactivity when calling nested.value, that said - I don't know how to make it reactive.
a little bit more digging and https://github.com/vuejs/pinia/discussions/854 finally gave me enough to come up with a (much more elegant) solution on my own.
<script lang="ts">
import { useStore } from "./store"
export default {
setup() {
const store = useStore()
return { store }
},
}
</script>
<template>
<h1>test</h1>
<input v-model="store.str" placeholder="obj" />
<input v-model="store.nested.obj" placeholder="str" />
</template>
FOR PINIA: destructuring the state checkout :storeToRefs()
In order to extract properties from the store while keeping its reactivity, you need to use storeToRefs(). It will create refs for every reactive property. This is useful when you are only using state from the store but not calling any action. Note you can destructure actions directly from the store as they are bound to the store itself too
<script>
import { useStore } from "./store"
import { storeToRefs } from 'pinia' // NOTE this
export default {
setup() {
const store = useStore()
const {str, nested } = storeToRefs(store)
return { str, nested }
},
}
</script>
<template>
<h1>test</h1>
<input v-model="str" placeholder="obj" />
<input v-model="nested.obj" placeholder="str" />
</template>
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>
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>