How to pass a ref to a child input component with Vue? - javascript

I'm currently trying to pass a ref to get the value of the input (base-input component) on submit. You will find below the two components. With the console.log in handleSubmit, email is always undefined.
Thanks in advance for your help.
Parent component
<template>
<form #submit.prevent="handleSubmit">
<div class="flex flex-col mt-10">
<form-label forInput="email" label="Email Address" />
<base-input type="email" name="email" ref="email" />
</div>
</form>
</template>
<script>
import BaseInput from "../UI/BaseInput.vue";
export default {
components: {
BaseInput,
},
methods: {
handleSubmit() {
const email = this.$refs.email.value;
console.log(email);
},
},
};
</script>
Child Input component
<template>
<input
:type="type"
:name="name"
:ref="name"
/>
</template>
<script>
export default {
props: ["type", "name"],
};
</script>

If you want to access the value from the child's input field in the parent component, you need a way for data to flow from child to parent component, and that is done through emits.
But wouldn't it be nice to be able to use v-model with your custom BaseInput component? The same way one would use form input binding?
<input
:value="text"
#input="event => text = event.target.value">
or v-model to simplify
<input v-model="text">
Something like this
<BaseInput v-model="email" />
Well, we can do just that. What you need is a modelValue prop and update:modelValue emit event.
You can wrap both inside a writeable computed property for a cleaner and clearer syntax.
const props = defineProps({
modelValue: {
type: String,
},
});
const emit = defineEmits(['update:modelValue']);
const internalValue = computed({
get() {
return props.modelValue;
},
set(value: string) {
return emit('update:modelValue', value);
},
});
When internalValue is set to a new value, it emits that event to the parent component and it syncs it through props.modelValue. Meaning, you can change props.modelValue in the parent component and the change would be reflected inside your custom component. And vice versa.
I like this approach since it gives you a very natural way of reasoning about your component. Now, this concept isn't restricted to v-model per se, you can use it with any prop you want to synchronize to the parent component. Just use name prop, update:name emit in child component and use v-model:name in parent component.
Resources:
https://vuejs.org/guide/components/events.html#usage-with-v-model

first the BaseInput is spelt wrong in the template.
Instead of
<base-input type="email" name="email" ref="email" />
you need to change it to
<BaseInput :type="'email'" :name="'email'" ref="email" />
A way better approach will be to use #emit()
Child.vue
<template>
<input
:type="type"
:name="name"
#change="$emit('inputContent', Content)"
v-model="Content"
/>
</template>
<script>
export default {
emits: ['inputContent'],
data() {
return {
Content: '',
}
},
props: ["type", "name"],
};
</script>
Don't forget to declare your props as strings. 😉
Parent.vue
<template>
<BaseInput :type="'email'" :name="'email'" ref="email" #inputContent="handleSubmit"/>
</template>
<script>
import BaseInput from "../UI/BaseInput.vue";
export default {
components: {
BaseInput,
},
methods: {
handleSubmit(content) {
const email = content;
console.log(email);
},
},
};
</script>
Here is something about emits in the vue docs
and how to use v-model
I hope this helps :)

Related

Binding child input :value to the parent prop

I try to bind child input value to the parent value. I pass value searchText from a Parent component to a Child component as prop :valueProp. There I assign it to property value: this.valueProp and bind input:
<input type="text" :value="value" #input="$emit('changeInput', $event.target.value)" />
The problem is that input doesn't work with such setup, nothing displays in input, but parent searchText and valueProp in child update only with the last typed letter; value in child doesn't update at all, though it is set to equal to searchText.
If I remove :value="value" in input, all will work fine, but value in child doesn't get updated along with parent's searchText.
I know that in such cases it's better to use v-model, but I want to figure out the reason behind such behavior in that case.
I can't understand why it works in such way and value in child component doesn't update with parent's searchText. Can you please explain why it behaves in that way?
Link to Sanbox: Sandbox
Parent:
<template>
<div>
<Child :valueProp="searchText" #changeInput="changeInput" />
<p>parent {{ searchText }}</p>
</div>
</template>
<script>
import Child from "./Child.vue";
export default {
name: "Parent",
components: { Child },
data() {
return {
searchText: "",
};
},
methods: {
changeInput(data) {
console.log(data);
this.searchText = data;
},
},
};
</script>
Child:
<template>
<div>
<input type="text" :value="value" #input="$emit('changeInput', $event.target.value)" />
<p>value: {{ value }}</p>
<p>child: {{ valueProp }}</p>
</div>
</template>
<script>
export default {
emits: ["changeInput"],
data() {
return {
value: this.valueProp,
};
},
props: {
valueProp: {
type: String,
required: true,
},
},
};
</script>
You set the value in your Child component only once by instantiating.
In the data() you set the initial value of your data properties:
data() {
return {
value: this.valueProp,
};
},
Since you don't use v-model, the value will never be updated.
You have the following options to fix it:
The best one is to use v-model with value in the Child.vue
<input
type="text"
v-model="value"
update value using watcher
watch: {
valueProp(newValue) {
this.value = newValue;
}
},
use a computed property for value instead of data property
computed: {
value() {return this.valueProp;}
}
Respect for creating the sandbox!
You are overwriting the local value every time the value changes
data() {
return {
value: this.valueProp, // Don't do this
};
},
Bind directly to the prop:
<input ... :value="valueProp" ... />

Vue - Keep input focused on route change

I have a TopNavBar component, that is present on every route. This component includes a search input field. When a user clicks on the input field the route changes from /bar to /foo but input focus is lost. How can I (re)focus on the input?
TopNavBar.vue
<template>
<input type="search" name="search-library" v-focus ref="searchInput" #focus="initSearch". />
</template>
<script setup>
const searchInput = ref(null);
<input type="search" name="search-library" v-focus ref="searchInput" #focus="initSearch". />
function initSearch() {
if (router.currentRoute.value.name != "/foo") {
router.push({ path: "/foo", query: { initSearch: true }, key: route.fullPath });
}
}
watch(
() => router.currentRoute.value.path,
(newRoute) => {
if (newRoute == "/foo") {
searchInput.value.focus();
}
}
);
</script>
I'm using Vue3 and Nuxt3. v-focusz directive is declared globally in /plugins` folder and works as expected.
Update
TopNavBar is inside Nuxt 3 layout. Also, upon further investigation I've realised that the input does focus on route change but immediately loses it again.
You can achieve this by using $refs, Attach a reference on input element and then call focus method on it.
In template:
<parent-component>
<search-component ref="searchComponentRef" />
</parent-component>
In script:
mounted() {
this.$refs.searchComponentRef.$el.focus();
}

What's the best way to trigger a child emit from the parent component?

Right now I'm passing a trigger prop from the parent to child component, which triggers the emit from the child to the parent.
parent component:
<form #submit.prevent="state.store=true" method="post" enctype="multipart/form-data">
<child-component :triggerEmit=state.store #emitSomething="getSomething()"/>
child component:
const emit = defineEmits([
'emitBody'
])
watchEffect(async () => {
if (props.triggerEmit) {
emit('emitSomething', value)
}
})
This gets confusing quickly, if the components grow in size and I was wondering if there is a simpler way to trigger child emits from the parent, since this seems to be a common use case.
Edit:
Trying to trigger the child method directly from the parent (not working).
child:
const childMethod = () => {
console.log('check')
}
parent:
html:
<child ref="childRef"/>
script setup:
const childRef = ref()
childRef.value.childMethod()
Page throws error:
Cannot read properties of undefined (reading 'childMethod')
As per my understanding you want to access multiple child component methods/properties from the parent component. If Yes, you can achieve that by create a ref and access the methods.
In template :
<!-- parent.vue -->
<template>
<button #click="$refs.childComponentRef.childComponentMethod()">Click me</button>
<child-component ref="childComponentRef" />
</template>
In script :
With Vue 2 :
this.$refs.childComponentRef.childComponentMethod( );
With Vue 3 Composition Api :
setup( )
{
const childComponentRef = ref( );
childComponentRef.value.childComponentMethod( )
return {
childComponentRef
}
}
In this case, the parent's trigger is effectively querying the child for its event data so that it could call getSomething() on it. The parent already owns getSomething(), so it really only needs the child data.
Another way to get that data is to use v-model to track the child data:
In the child component, implement v-model for a prop (a string for example) by declaring a modelValue prop and emitting an 'update:modelValue' event with the new value as the event data:
<!-- ChildName.vue -->
<script setup>
defineProps({ modelValue: String })
defineEmits(['update:modelValue'])
</script>
<template>
<label>Name
<input type="text" :value="modelValue" #input="$emit('update:modelValue', $event.target.value)">
</label>
</template>
In the parent, add a reactive object, containing a field for each child's v-model:
<!-- ParentForm.vue -->
<script setup>
import { reactive } from 'vue'
const formData = reactive({
name: '',
age: 0,
address: {
city: '',
state: '',
},
})
</script>
<template>
<form>
<child-name v-model="formData.name" />
<child-age v-model="formData.age" />
<child-address v-model:city="formData.address.city" v-model:state="formData.address.state" />
<button>Submit</button>
</form>
</template>
Now, the parent can call getSomething() on each field upon submitting the form:
<!-- ParentForm.vue -->
<script setup>
import { toRaw } from 'vue'
â‹®
const getSomething = field => {
console.log('getting', field)
}
const submit = () => {
Object.entries(toRaw(formData)).forEach(getSomething)
}
</script>
<template>
<form #submit.prevent="submit">
â‹®
</form>
</template>
demo

Combine vue props with v-for

Here's sample of my child component
HTML:
<div v-for="(input, index) in form.inputs" :key="index">
<div>
<input :name"input.name" :type="input.type" />
</div>
</div>
JavaScript (Vue):
<script>
export default {
name: "child",
props: ['parentForm'],
data() {
return {
form: {
inputs: [
{
name: 'name',
type: 'text'
],
[...]
}
}
}
And sample of root component
HTML:
<child :parentsForm="form"></child>
JavaScript (Vue):
<script>
import child from "./child";
export default {
name: "root",
components: { child },
data() {
return {
form: {
data: {
name: null,
email: null,
...
}
}
}
The question is, how do I achieve combining root + v-for?
Example I want to using child component this way
<input :name"input.name" :type="input.type" v-model="parentForm.data . input.name" />
Since parentForm.data will bind form:data:{ and this will be the variable get from input.name }
Output in v-model should be bind form.data.name or form.data.email on root component
Thank you
You can use it as per follow,
<input :name="input.name" :type="input.type" v-model="parentForm.data[input.name]" />
This will bind parentForm.data.name for input.name = 'name' to v-model.
If I understood you correctly, you want to update parent data from your child component. If yes then you have two options.
In you child component use $parent.form.data to bind.
Or you can pass it down as prop assign it to a data property in child. Bind this new data property in your child and emit it whenever any changes are made. Receive this emit in your parent and update the parent property respectively (Recommended)

How pass a v-model on the parent into a template

I'm trying to compose the UI for a search page, but wanna use components to reuse code. However, I need a way to pass the model of the page to the search component, and don't see how:
In index.html:
<template id="search">
<q-search inverted placeholder="Look" float-label="Search" v-model="search" /> <-- BIND HERE
</template>
<template id="ListCustomersPage">
<q-layout>
<q-layout-header>
<search v-model="search"></search> <-- HOW PASS INTO THIS
</q-layout-header>
</q-layout>
</template>
And the code:
const search = {
template: '#search',
props: ['search']
};
const ListCustomersPage = {
key: 'ListCustomersPage',
template: '#ListCustomersPage',
components: { search },
data() {
return {
title: 'Select Customer',
search:'' <-- FROM THIS TO 'BIND HERE'
}
}
};
I'm not sure if I'm 100% following what you're asking, but it seems like you just want to pass a property to a child component?
<search :search="search"></search> <-- HOW PASS THIS
Passing a prop to a child is done with v-bind or the colon short hand for it.
<child-component :property="parent_data"></child-component>
<child-component v-bind:property="parent_data"></child-component>
See the documentation here.

Categories

Resources