Vue.js: Child component omits the `ref` attribute - vue.js

I have created a component called Input. All the attributes that are passed to this Input component are inherited successfully, but the ref attribute is always omitted for some reason.
I need the ref attribute to set focus on the element, after the validation fails.
Some.vue
<template>
...
<Input
type="number"
name="mobile"
ref="mobileRef"
v-model="this.form.mobile"
:class="errors.mobile ? 'error' : null"
:error="errors.mobile ?? null"
/>
...
</template>
<script>
import Input from '#Inputs/Input';
export default {
...
components: {
Input,
},
...
}
</script>
Input.vue
<template>
<input
autocomplete="none"
class="form-control"
:ref="ref"
:class="this.class"
:type="this.type ?? 'text'"
:value="modelValue"
:disabled="disabled"
#input="$emit('update:modelValue', $event.target.value)"
/>
</template>
<script>
export default {
name: 'Input',
inheritAttrs: false,
props: ['type', 'class', 'error', 'modelValue', 'disabled', 'ref']
}
</script>
I even hardcoded the ref attribute with some static value, but the result is same, omitted?
What am I doing wrong here..?

ref is a special attribute, it's not supposed to work as a prop.
It's possible to access root element of a component as $refs.mobileRef.$el.focus(). The component can also expose all public methods explicitly, so they would be used as $refs.mobileRef.focus().

You can remove the ref prop in your Input.vue component and add a watcher inside, to watch for some errors. If there is, trigger a .focus() on your input element.
Some.vue
<template>
...
<Input
type="number"
name="mobile"
v-model="this.form.mobile"
:class="errors.mobile ? 'error' : null"
:error="errors.mobile ?? null"
/>
...
</template>
<script>
import Input from '#Inputs/Input';
export default {
...
components: {
Input,
},
...
}
</script>
Input.vue
<template>
<input
autocomplete="none"
class="form-control"
ref="mobileRef"
:class="this.class"
:type="this.type ?? 'text'"
:value="modelValue"
:disabled="disabled"
#input="$emit('update:modelValue', $event.target.value)"
/>
</template>
<script>
export default {
name: 'Input',
inheritAttrs: false,
props: ['type', 'class', 'error', 'modelValue', 'disabled'],
watch: {
error(val) {
if (val)
this.$refs.mobileRef.focus();
},
},
}
</script>

Related

How to pass class attribute to child component element

how can I pass class attribute from parent to child component element?
Look here:
https://codesandbox.io/s/pedantic-shamir-1wuuv
I'm adding class to the Component "Input Field"
And my goal is that the 'custom-class' will be implemented on the 'input' element in the child component
But just still using the class attribute, and not setting a new prop like "customClass" and accept it in the props of the component
Thanks!
This depends on the template structure of your ChildComponent
Your Parent Component can look like this:
<div id="app">
<InputField class="anyClass" />
</div>
If your Child looks like this:
<template>
<input ... />
</template
Because if you have only 1 root Element in your template this will inherit the classes given automatically.
if your Template e.g. looks like this: (only available in vue3)
<template>
<input v-bind="$attrs" />
<span> hi </span>
</template
You will need the v-bind="$attrs" so Vue knows where to put the attributes to. Another Solution would be giving classes as props and assigning it to the element with :class="classes"
The pattern for the customs form component in Vue 2 where the props go to a child element looks something like this.
<template>
<div class="input-field">
<label v-if="label">{{ label }}</label>
<input
:value="value"
:class="inputClass"
#input="updateValue"
v-bind="$attrs"
v-on="listeners"
/>
</div>
</template>
<script>
export default {
inheritAttrs: false,
props: {
label: {
type: String,
default: "",
},
value: [String, Number],
inputClass: {
type: String,
default: "",
},
},
computed: {
listeners() {
return {
...this.$listeners,
input: this.updateValue,
};
},
},
methods: {
updateValue(event) {
this.$emit("input", event.target.value);
},
},
};
</script>
The usage of those components could look something like this.
```html
<template>
<div id="app">
<InputField
v-model="name"
label="What is your name?"
type="text"
class="custom"
inputClass="input-custom"
/>
<p>{{ name }}</p>
</div>
</template>
<script>
import InputField from "./components/InputField";
export default {
name: "App",
components: {
InputField,
},
data() {
return {
name: "",
};
},
};
</script>
A demo is available here
https://codesandbox.io/s/vue-2-custom-input-field-4vldv?file=/src/components/InputField.vue
You need to use vue props . Like this:
child component:
<template>
<div>
<input :class="className" type="text" value="Test" v-bind="$attrs" />
</div>
</template>
<script>
export default {
props:{
className:{
type:String,
default:'',
}
}
};
</script>
Parent component:
<div id="app">
<InputField :class-name="'custom-class'" />
</div>
Since you're using vue2 the v-bind=$attrs is being hooked to the root element of your component the <div> tag. Check the docs. You can put this wrapper on the parent element if you need it or just get rid of it.
Here is a working example
Another approach
There is also the idea of taking the classes from the parent and passing it to the child component with a ref after the component is mounted
Parent Element:
<template>
<div id="app">
<InputField class="custom-class" ref="inputField" />
</div>
</template>
<script>
import InputField from "./components/InputField";
export default {
name: "App",
components: {
InputField,
},
mounted() {
const inputField = this.$refs.inputField;
const classes = inputField.$el.getAttribute("class");
inputField.setClasses(classes);
},
};
</script>
Child Element:
<template>
<div>
<input type="text" value="Test" :class="classes" />
</div>
</template>
<script>
export default {
data() {
return {
classes: "",
};
},
methods: {
setClasses: function (classes) {
this.classes = classes;
},
},
};
</script>
Here a working example
In Vue2, the child's root element receives the classes.
If you need to pass them to a specific element of your child component, you can read those classes from the root and set them to a specific element.
Example of a vue child component:
<template>
<div ref="root">
<img ref="img" v-bind="$attrs" v-on="$listeners" :class="classes"/>
</div>
</template>
export default {
data() {
return {
classes: null,
}
},
mounted() {
this.classes = this.$refs.root.getAttribute("class")
this.$refs.root.removeAttribute("class")
},
}
Pretty much it.

The Value of Variables does not update inside components

When I put the input field inside a component, it doesn't update the value by any means. Is there something I've missed? Btw, I use Vue 3.
Child Component (input-form.vue)
<template>
<input
type="text"
:value="searchField"
#input="$emit('update:searchField', $event.target.value)"
/>
<p>Text Inside: {{ searchField }}</p>
</template>
<script>
export default {
emits: ["update:searchField"],
props: {
searchField: {
type: String,
},
},
};
</script>
Parent
<template>
<div>
<input-form :searchField="searchField" />
<p>Text Outside: {{ searchField }}</p>
</div>
</template>
<script>
import inputForm from "components/input-form.vue";
export default {
data() {
return {
searchField: "",
};
},
components: {
inputForm,
},
};
</script>
You are not listening for the update:searchField event in the Parent.
<input-form :searchField="searchField" #update:searchField="searchField = $event" />
or
<input-form v-model:searchField="searchField" />

Passing data with props defined in grandparent to grandchild

Parent class:
<template>
<BaseButton icon="activity">Slotted Text</BaseButton>
</template>
Child class: BaseButton
<template>
<div>
{{ icon }} // returns the expected result; prop is coming in.
<slot>Default Text</slot>
<BaseIcon :name="{{icon}}" /> <-- error is here, see below.
</div>
</template>
<script>
export default {
props: {
icon: {
type: String,
required: true,
},
},
}
Grandchild:
<template>
<div class="icon-wrapper" v-html="svg"></div>
</template>
<script>
export default {
props: {
name: String
}
// code that uses the name prop
}
</script>
The error is:
Unexpected token '{' in
{{icon}}
Is there a way to pass the expression into the prop?
You should use the binding v-bind:name="icon" to bind the attribute with the property :
<BaseIcon v-bind:name="icon" />
or the shorthand :
<BaseIcon :name="icon" />

Vue v-model bind to Parent component input element doesn't work

I am using Vue, Nuxt, Vue Good Table to live search a table. I create a child component named child.vue, and imported it into the parent page parent.vue. I used v-model to bind the searchTerm on the input element in the parent component. When I run it, live search doesn't work. And I got an warning from the console. Property or method "searchTerm" is not defined on the instance but referenced during render. Could you please help me out? I am new to Vue.js. Thank you
For the child.vue
<template>
<vue-good-table
:columns="columns"
:rows="rows"
:search-options="{
enabled: true,
externalQuery: searchTerm
}"
:sort-options="{
enabled: false,
}"
:group-options="{
enabled: true,
}"
/>
</template>
<script>
export default {
name: 'my-component',
data() {
return {
searchTerm: '',
columns: [xxxx]
}
}
}
</script>
For the parent.vue
<template>
<div>
<input type="text" placeholder="Live Search" v-model="searchTerm" />
</div>
<div>
<child />
</div>
</template>
Pass the prop from the parent to the child, and define searchTerm in the parents data
<template>
...
<input type="text" placeholder="Live Search" v-model="searchTerm" />
<child :searchTerm="searchTerm" />
...
</template>
<script>
data(){
return {
searchTerm: ''
}
}
...
</script>
Define the prop in the child, and remove searchTerm in the childs data
<script>
export default {
...
props: ['searchTerm'],
...
data(){
return {
//searchTerm: '' <- remove this from child
}
}
...
}
</script>
You can now access searchTerm in the childs <template> or this.$props.searchTerm in the childs <script>

Vue: How do you bind a value to an input through a wrapper component?

I know you can use v-model to bind a value to an input in the same component. How do you create a wrapper component for an input and bind a value to it?
Login.vue
<template>
<div id="Login">
<Input v-bind:value="email"/>
<Input v-bind:value="password"/>
</div>
</template>
<script>
import Input from './Input.vue'
import Button from './Button'
export default {
name: 'Login',
components: {
Input,
Button,
},
data: () => ({
email:'test',
password:'test',
}),
methods: {
login: () => { debugger; }, //this.email and this.password are still set to test
}
}
</script>
Input.vue
<template>
<div class="input>
<input v-model="value"/>
</div>
</template>
<script>
export default {
name: 'Input',
props: {
value: String,
},
}
</script>
Current set up results in
[Vue warn]: Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: "value"
Is the only way to do this by emitting an event?
If I got it correctly, you can try to create transparent wrapper (in my case AppInput)
SomeParent.vue
<div>
<AppInput v-model="parentModel" />
</div>
AppInput.vue
<template>
<input
class="app-input"
v-bind="$attrs"
:value="value"
v-on="{
...$listeners,
input: event => $emit('input', event.target.value)
}">
</template>
<script>
export default {
name: "AppInput",
inheritAttrs: false,
props: ["value"]
};
</script>
one of articles
The best way is use v-model for wrapper and on/emit for input
<div id="Login">
<Input v-model="email"/>
<Input v-model="password"/>
</div>
...
<div class="input>
<input
v-bind:value="value"
v-on:input="$emit('input', $event.target.value)"
>
</div>
You can implement v-model directly in the input component by doing so.
<template>
<div class="input>
<input :value="value" #input="$emit('input', $event.target.value)"/>
</div>
</template>
<script>
export default {
name: 'Input',
props: ["value"]
}
</script>
And then use it in your parent component like this:
<template>
<div id="Login">
<Input v-model="email"/>
<Input v-model="password"/>
</div>
</template>
<script>
import Input from './Input.vue'
import Button from './Button'
export default {
name: 'Login',
components: {
Input,
Button,
},
data: () => ({
email:'test',
password:'test',
}),
methods: {
login: () => { debugger; }, //this.email and this.password are still set to test
}
}
</script>
See here