How to declare local property from composition API in Vue 3? - vue.js

In Vue 2 I would do this:
<script>
export default {
props: ['initialCounter'],
data() {
return { counter: this.initialCounter }
}
}
</script>
In Vue 3 I tried this:
<script setup>
import { ref } from 'vue';
defineProps({ 'initialCounter': Number })
const counter = ref(props.initialCounter)
</script>
This obviously doesn't work because props is undefined.
How can I bind one-way properties to a local variable in Vue 3?

It seems the result of defineProps is not assigned as a variable. check Vue3 official doc on defineProps. Not really sure what is the use case of ref() here but toRef API can be used as well.
import { ref } from 'vue';
const props = defineProps({ 'initialCounter': Number })
const counter = ref(props.initialCounter)

Retrieving a read-only value and assigning it to another one is a bad practice if your component is not a form but for example styled input. You have an answer to your question, but I want you to point out that the better way to change props value is to emit update:modalValue of parent v-model passed to child component.
And this is how you can use it:
<template>
<div>
<label>{{ label }}</label>
<input v-bind="$attrs" :placeholder="label" :value="modelValue" #input="$emit('update:modelValue', $event.target.value)">
<span v-for="item of errors">{{ item.value }}</span>
</div>
</template>
<script setup>
defineProps({ label: String, modelValue: String, errors: Array })
defineEmits(['update:modelValue'])
</script>
v-bind="$attrs" point where passed attributes need to be assigned. Like type="email" attribute/property of a DOM element.
And parent an email field:
<BaseInput type="email" v-model="formData.email" :label="$t('sign.email')" :errors="formErrors.email" #focusout="validate('email')"/>
In this approach, formdata.email in parent will be dynamically updated.

Related

Vue3 - API Composition : Custom v-model prop

In Vue2, the v-model bind to the prop value, but it's possible to define a other prop :
Vue.component('base-checkbox', {
model: {
prop: 'checked',
event: 'change'
},
props: {
checked: Boolean
},
template: `
<input
type="checkbox"
v-bind:checked="checked"
v-on:change="$emit('change', $event.target.checked)"
>
`
})
I want do the same in Vue 3 with API Composition.
How do you define a custom prop to v-model in Vue 3 with the API Composition?
Example :
<script setup lang="ts">
defineProps({
msg: {
type: String,
}
});
defineEmits(['update:msg'])
defineModel('msg', 'update:msg');
</script>
<template>
<div>
{{ msg }}
</div>
<button #click="$emit('update:msg', 'Clicked!')">Switch</button>
</template>
Of course defineModel don't exist, but I must not be far from the truth.
In Vue 3 the model option are removed and replaced with an argument on v-model
So if you want to change the v-model prop you can by changing the defineProps and defineEmits to use another name than modelValue.
However you cannot implicitly use v-model without adding the new custom prop name after the v-model.
You have to specify it like this : v-model:myCustomPropName.
<template>
<input :value="myCustomPropName" #input="$emit('update:myCustomPropName', $event.target.value)" />
</template>
<script setup>
import { defineProps, defineEmits } from "vue";
defineProps(["myCustomPropName"]);
defineEmits(["update:myCustomPropName"]);
</script>
Usage:
<MyCustomInput v-model:myCustomPropName="msg" />
Vue 3 props Doc : https://vuejs.org/guide/components/v-model.html
See the changes from Vue 2 to Vue 3 : https://v3-migration.vuejs.org/breaking-changes/v-model.html

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?

How to correctly pass a v-model down to a Quasar q-input base component?

I am using Quasar to build my Vue app and I want to create a base component (a.k.a. presentational, dumb, or pure component) using q-input.
I have a created a SFC named VInput.vue as my base component, it looks like this:
<template>
<q-input
outlined
class="q-mb-md"
hide-bottom-space
/>
</template>
Then I created a SFC named TestForm.vue that looks like this:
<template>
<q-form>
<v-input label="Email" v-model="email" />
</q-form>
</template>
<script setup lang="ts">
import VInput from './VInput.vue';
import { ref } from 'vue';
const email = ref('john#example.com');
</script>
The label="Email" v-model="email" parts are passed down to my VInput.vue base component and correctly rendered on the page.
But there is a typescript error on q-input of the VInput.vue base component because q-input requires a v-model:
`Type '{ outlined: true; class: string; hideBottomSpace: true; "hide-bottom-space": boolean; }' is not assignable to type 'IntrinsicAttributes & VNodeProps & AllowedComponentProps & ComponentCustomProps & QInputProps'.`
`Property 'modelValue' is missing in type '{ outlined: true; class: string; hideBottomSpace: true; "hide-bottom-space": boolean; }' but required in type 'QInputProps'.ts(2322)`.
So how do I code the VInput.vue base component without knowing the v-model value head of time?
I have come up with the below solution, which seems to work because I think the v-model passed down is overiding the base component v-model.
But I wanted to ask to make sure I wasn't screwing something up.
Is this the correct way of doing things? It seems hacky.
<template>
<q-input v-model="inputText" outlined class="q-mb-md" hide-bottom-space />
</template>
<script setup lang="ts">
const inputText = '';
</script>
I found a couple of solutions:
Solution 1
It involves splitting the v-model into it seperate parts (:model-value and #update:model-value, and then passing in the text value as a prop.
Base component VInput.vue:
<template>
<q-input
outlined
class="q-mb-md"
hide-bottom-space
:model-value="text"
#update:model-value="(value) => emit('update:text', value)"
/>
</template>
<script setup lang="ts">
defineProps({
text: {
required: false,
type: String,
},
});
const emit = defineEmits(['update:text']);
</script>
Solution 2
Extracting the prop and using toRef on it.
<template>
<q-input outlined class="q-mb-md" hide-bottom-space v-model="textProp" />
</template>
<script setup lang="ts">
import { toRef } from 'vue';
const props = defineProps({
text: {
required: false,
type: String,
default: '',
},
});
const textProp = toRef(props, 'text');
</script>

Vue js how to use props values to v-model

I have two component namely App.vue and hello.vue
In App component I import the hello component and use props to pass relevant data to the hello component.
there I bind data which are took from the App component.
In my hello component I have a input box bind to the passed value.
My final goal is pass values as props to the hello component and change it and finally
pass that edited values to the backend using the save method.
How do I achive this?
This is what I have done up to now.
App.vue
<template>
<div id="app">
<hello-world :msg="'hello good morning'"></hello-world>
</div>
</template>
<script>
import helloWorld from "./components/HelloWorld";
export default {
components: {
helloWorld
}
};
</script>
hello.vue
<template>
<div>
<input type="text" :value="msg">
</div>
</template>
<script>
export default {
name: "HelloWorld",
props: {
msg: String
}
};
</script>
In my hello component's input field v-model is not possible. I want something similar to the v-model.
You cannot use prop to bind to v-model. Child component is not supposed to modify prop passed by the parent component.
You will have to create a copy of prop in your child component if you wish to use prop with v-model and then watch prop like this:
<template>
<div>
<input type="text" #input="onInput" v-model="msgCopy">
</div>
</template>
<script>
export default {
name: "HelloWorld",
props: {
msg: String
},
data() {
return { msgCopy: '' };
},
methods: {
onInput(newInputValue) {
this.$emit('msgChange', newInputValue);
}
}
watch: {
msg(newVal) {
this.msgCopy = newVal;
}
}
};
</script>
Also, notice the use of event handler #input to pass changed prop back to the parent component via event. As a syntax sugar, you can make your Hello component work as a custom form input control by adopting to v-model lifecycle.

How to pass all props dynamically to child component

How to pass all props dynamically to child component? As an example consider a case:
// wrapper around a component my-wrapper.vue
<template>
<div>
<third-party-component />
</div>
</template>
third-party-component is a component which can accept number of attributes like value, disabled, clicked, etc. How I can use my-wrapper in way that whatever I passed as props to it it will be transferred to third-party-component like
<my-wrapper :value="myVal" :disabled="field.isDisabled" />
By default the attributes you add on to my-wrapper will be bound to the root element which is div. To avoid this set inheritAttrs option to false
Then you can bind all the attributes to using v-bind="$attrs" where $attrs contains parent-scope attribute bindings (except for class and style)
// wrapper around a component my-wrapper.vue
<template>
<div>
<third-party-component v-bind="$attrs"/>
</div>
</template>
<script>
export default{
inheritAttrs: false
}
</script>
I would set identical props to the wappers and use them in the template ...
But there is many ways, it easy to speak from parent to childs.
https://en.vuejs.org/v2/guide/components-props.html#Passing-Static-or-Dynamic-Props
https://v1.vuejs.org/guide/syntax.html#Shorthands
---- MyWrapper.vue
<template>
<div>
<third-party-component :value="value" :disabled="disabled" />
</div>
</template>
<script>
#import third-party-component from '../..? ./components/thirdPartyComponent.vue'
export default {
name: 'MyWrapper',
props: ['value', 'disabled'],
components: {
third-party-component // local component, you can use global
}
//... data, computed ...
}
</script>
----- MyAppli.vue
<template>
<my-wrapper :value="myVal" :disabled="field.isDisabled" />
</template>
<script>
import my-wrapper from '../..? ../components/MyWrapper.vue'
export default {
name: 'MyApp',
components: {
my-wrapper // local component, you can use global
} ....