Building a Controlled Input Component in Vue.js
In Vue.js, controlled components are a fundamental pattern for managing form input state. They allow you to synchronize the component's state with the DOM element's value, providing greater control over input behavior and validation. This challenge will guide you through building a reusable controlled input component.
Problem Description
Your task is to create a custom Vue.js component named ControlledInput.vue that functions as a controlled input field. This component should accept a modelValue prop (for two-way data binding with v-model) and emit an update:modelValue event when the input's value changes. This will enable parent components to manage the input's state externally.
Key Requirements:
- Component Structure: Create a single-file Vue component (
ControlledInput.vue). - Input Element: The component should render a standard HTML
<input type="text">element. modelValueProp: Accept a prop namedmodelValueof typestring(or any appropriate type if you want to extend this later, butstringis sufficient for this challenge). This prop represents the current value of the input field.update:modelValueEvent: Emit an event namedupdate:modelValuewhenever the user types into the input field. The payload of this event should be the new value of the input field.- Two-way Data Binding: The component should seamlessly integrate with Vue's
v-modeldirective.
Expected Behavior:
When ControlledInput is used in a parent component with v-model, typing into the input field should update the parent component's data, and changes to the parent component's data should be reflected in the input field.
Edge Cases to Consider:
- Initial Value: The input should correctly display its initial
modelValuewhen the component is first rendered. - Empty Input: The component should handle an empty
modelValuegracefully.
Examples
Example 1: Basic Usage
Parent Component (App.vue):
<template>
<div>
<ControlledInput v-model="message" />
<p>Message in parent: {{ message }}</p>
</div>
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue';
import ControlledInput from './ControlledInput.vue';
export default defineComponent({
components: {
ControlledInput,
},
setup() {
const message = ref('Hello!');
return {
message,
};
},
});
</script>
ControlledInput.vue (expected implementation):
<template>
<input
type="text"
:value="modelValue"
@input="onInput"
/>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
export default defineComponent({
props: {
modelValue: {
type: String,
required: true,
},
},
emits: ['update:modelValue'],
setup(props, { emit }) {
const onInput = (event: Event) => {
const target = event.target as HTMLInputElement;
emit('update:modelValue', target.value);
};
return {
onInput,
};
},
});
</script>
Output in browser:
The input field will display "Hello!". When you type " World" into the input, it becomes "Hello World". The paragraph below the input will also update to "Message in parent: Hello World".
Explanation:
The ControlledInput component receives "Hello!" via the modelValue prop. When the user types, the @input event is triggered, calling onInput. onInput then emits update:modelValue with the new input value, which v-model in the parent component captures and updates the message ref.
Example 2: Initial Empty Value
Parent Component (App.vue):
<template>
<div>
<ControlledInput v-model="username" />
<p>Username in parent: {{ username }}</p>
</div>
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue';
import ControlledInput from './ControlledInput.vue';
export default defineComponent({
components: {
ControlledInput,
},
setup() {
const username = ref(''); // Initial empty value
return {
username,
};
},
});
</script>
ControlledInput.vue (same as Example 1):
<template>
<input
type="text"
:value="modelValue"
@input="onInput"
/>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
export default defineComponent({
props: {
modelValue: {
type: String,
required: true,
},
},
emits: ['update:modelValue'],
setup(props, { emit }) {
const onInput = (event: Event) => {
const target = event.target as HTMLInputElement;
emit('update:modelValue', target.value);
};
return {
onInput,
};
},
});
</script>
Output in browser:
The input field will be empty.
As the user types into the input, the username ref in the parent component will be updated, and the paragraph will display the entered text.
Explanation:
This demonstrates that the component correctly handles an initial empty modelValue.
Constraints
- The
ControlledInput.vuecomponent must be written in TypeScript. - The component must use the Options API or Composition API as you see fit, but demonstrate a clear understanding of how to handle props and emits.
- The solution should be a single Vue component file.
- No external libraries beyond Vue.js itself are permitted.
Notes
- Consider how the
v-modeldirective internally works with props and events. This challenge aims to replicate that behavior for a custom input. - Pay attention to type safety when handling the DOM event and extracting the input's value.
- Think about accessibility: while not a primary requirement for this challenge, in a real-world scenario, you would add attributes like
aria-labelfor better accessibility. - You can extend this component later to handle different input types (e.g.,
password,number,email) or add validation.