Using modelValue inside a nested div instead of the wrapping element.
I have an input and I am applying its modelValue inside of it. The problem is that modelValue come has a prop and I can't update its value. So I created a ref out of the modelValue and I am using as such.
I just want to make sure I am doing the things properly, I wonder if there is a better approach to it. It's working btw
<template>
<div class="flex flex-col" :class="extraClasses">
<div v-if="label" class="flex mb-2 text-blue">
<p v-if="required" class="mr-1 text-xs text-error-600">*</p>
<p
class="text-sm font-semibold"
:class="[disabled ? 'text-gray-300' : 'text-gray-800']"
>
{{ label }}
</p>
</div>
<input
v-if="variant"
v-model="modelValue"
:name="name"
class="overflow-hidden overflow-ellipsis px-3 py-2 font-normal text-base rounded-lg placeholder:text-gray-300 placeholder:text-base"
:class="[
outline ? 'outline outline-2' : '',
errorMessage
? 'outline-error-600 color-error-600'
: 'outline-gray-400 focus:outline-gray-700',
]"
:type="type"
:placeholder="placeholder ?? name"
:disabled="disabled"
:required="required"
v-bind="$attrs"
/>
<textarea
v-else
v-model="modelValue"
:name="name"
class="overflow-hidden overflow-ellipsis px-3 py-2 font-normal text-base rounded-lg placeholder:text-gray-300 placeholder:text-base"
:class="[
outline ? 'outline outline-2' : '',
errorMessage
? 'outline-error-600 color-error-600'
: 'outline-gray-400 focus:outline-gray-700',
]"
cols="30"
rows="10"
:placeholder="placeholder ?? name"
:disabled="disabled"
:required="required"
v-bind="$attrs"
/>
<span v-if="!noErrors" class="mt-1 text-error-500 h-3 text-[10px]">{{
errorMessage
}}</span>
</div>
</template>
<template>
<div class="flex flex-col" :class="extraClasses">
<div v-if="label" class="flex mb-2 text-blue">
<p v-if="required" class="mr-1 text-xs text-error-600">*</p>
<p
class="text-sm font-semibold"
:class="[disabled ? 'text-gray-300' : 'text-gray-800']"
>
{{ label }}
</p>
</div>
<input
v-if="variant"
v-model="modelValue"
:name="name"
class="overflow-hidden overflow-ellipsis px-3 py-2 font-normal text-base rounded-lg placeholder:text-gray-300 placeholder:text-base"
:class="[
outline ? 'outline outline-2' : '',
errorMessage
? 'outline-error-600 color-error-600'
: 'outline-gray-400 focus:outline-gray-700',
]"
:type="type"
:placeholder="placeholder ?? name"
:disabled="disabled"
:required="required"
v-bind="$attrs"
/>
<textarea
v-else
v-model="modelValue"
:name="name"
class="overflow-hidden overflow-ellipsis px-3 py-2 font-normal text-base rounded-lg placeholder:text-gray-300 placeholder:text-base"
:class="[
outline ? 'outline outline-2' : '',
errorMessage
? 'outline-error-600 color-error-600'
: 'outline-gray-400 focus:outline-gray-700',
]"
cols="30"
rows="10"
:placeholder="placeholder ?? name"
:disabled="disabled"
:required="required"
v-bind="$attrs"
/>
<span v-if="!noErrors" class="mt-1 text-error-500 h-3 text-[10px]">{{
errorMessage
}}</span>
</div>
</template>
3 Replies
<script setup lang="ts">
export type TextFieldType = "text" | "textarea" | "number" | "password";
export interface TextFieldProps {
modelValue: string | number;
name: string;
placeholder?: string;
label?: string;
required?: boolean;
outline?: boolean;
errorMessage?: string;
type?: TextFieldType;
size?: Size;
disabled?: boolean;
noErrors?: boolean;
extraClasses?: string;
}
const props = withDefaults(defineProps<TextFieldProps>(), {
// info: undefined,
label: undefined,
modelValue: undefined,
schema: undefined,
errorMessage: undefined,
type: "text",
size: "medium",
extraClasses: undefined,
placeholder: undefined,
});
const { modelValue } = toRefs(props);
const variant = computed(() =>
["text", "number", "password"].includes(props.type)
);
<script setup lang="ts">
export type TextFieldType = "text" | "textarea" | "number" | "password";
export interface TextFieldProps {
modelValue: string | number;
name: string;
placeholder?: string;
label?: string;
required?: boolean;
outline?: boolean;
errorMessage?: string;
type?: TextFieldType;
size?: Size;
disabled?: boolean;
noErrors?: boolean;
extraClasses?: string;
}
const props = withDefaults(defineProps<TextFieldProps>(), {
// info: undefined,
label: undefined,
modelValue: undefined,
schema: undefined,
errorMessage: undefined,
type: "text",
size: "medium",
extraClasses: undefined,
placeholder: undefined,
});
const { modelValue } = toRefs(props);
const variant = computed(() =>
["text", "number", "password"].includes(props.type)
);
Use defineModel instead.
const input = defineModel('input')
const input = defineModel('input')
I'm refactoring the code rn and it isn't quite working anymore.
usage
the fields are getting updated, but
<template>
<div class="flex flex-col" :class="extraClasses">
<label v-if="label" class="flex mb-2 text-blue">
<p v-if="required" class="mr-1 text-xs text-error-600">*</p>
<p
class="text-sm font-semibold"
:class="[disabled ? 'text-gray-300' : 'text-gray-800']"
>
{{ label }}
</p>
</label>
<component
:is="variant ? 'input' : 'textarea'"
:id="name"
v-model="input"
:name="name"
class="overflow-hidden overflow-ellipsis font-normal text-base rounded-lg placeholder:text-gray-300 placeholder:text-base"
:class="inputClasses"
:type="type"
:placeholder="placeholder ?? name"
:disabled="disabled"
:required="required"
v-bind="$attrs"
:aria-describedby="errorMessage ? `${name}-error` : null"
/>
<span
v-if="!noErrors"
:id="`${name}-error`"
class="mt-1 text-error-500 h-3 text-[10px]"
>{{ errorMessage }}</span
>
</div>
</template>
<template>
<div class="flex flex-col" :class="extraClasses">
<label v-if="label" class="flex mb-2 text-blue">
<p v-if="required" class="mr-1 text-xs text-error-600">*</p>
<p
class="text-sm font-semibold"
:class="[disabled ? 'text-gray-300' : 'text-gray-800']"
>
{{ label }}
</p>
</label>
<component
:is="variant ? 'input' : 'textarea'"
:id="name"
v-model="input"
:name="name"
class="overflow-hidden overflow-ellipsis font-normal text-base rounded-lg placeholder:text-gray-300 placeholder:text-base"
:class="inputClasses"
:type="type"
:placeholder="placeholder ?? name"
:disabled="disabled"
:required="required"
v-bind="$attrs"
:aria-describedby="errorMessage ? `${name}-error` : null"
/>
<span
v-if="!noErrors"
:id="`${name}-error`"
class="mt-1 text-error-500 h-3 text-[10px]"
>{{ errorMessage }}</span
>
</div>
</template>
<script setup lang="ts">
export type TextFieldType = "text" | "textarea" | "number" | "password";
export interface TextFieldProps {
name: string;
placeholder?: string;
label?: string;
required?: boolean;
outline?: boolean;
errorMessage?: string;
type?: TextFieldType;
size?: Size;
disabled?: boolean;
noErrors?: boolean;
extraClasses?: string;
noPadding?: boolean;
}
const props = withDefaults(defineProps<TextFieldProps>(), {
label: undefined,
schema: undefined,
errorMessage: undefined,
type: "text",
size: "medium",
extraClasses: undefined,
placeholder: undefined,
});
const input = defineModel<string | number>("input");
const variant = computed(() =>
["text", "number", "password"].includes(props.type)
);
const inputClasses = computed(() => [
"overflow-hidden overflow-ellipsis font-normal text-base rounded-lg placeholder:text-gray-300 placeholder:text-base",
props.outline ? "outline outline-2" : "",
props.errorMessage
? "outline-error-600 color-error-600"
: "outline-gray-400 focus:outline-gray-700",
props.noPadding ? "p-0" : "px-3 py-2",
]);
</script>
<script setup lang="ts">
export type TextFieldType = "text" | "textarea" | "number" | "password";
export interface TextFieldProps {
name: string;
placeholder?: string;
label?: string;
required?: boolean;
outline?: boolean;
errorMessage?: string;
type?: TextFieldType;
size?: Size;
disabled?: boolean;
noErrors?: boolean;
extraClasses?: string;
noPadding?: boolean;
}
const props = withDefaults(defineProps<TextFieldProps>(), {
label: undefined,
schema: undefined,
errorMessage: undefined,
type: "text",
size: "medium",
extraClasses: undefined,
placeholder: undefined,
});
const input = defineModel<string | number>("input");
const variant = computed(() =>
["text", "number", "password"].includes(props.type)
);
const inputClasses = computed(() => [
"overflow-hidden overflow-ellipsis font-normal text-base rounded-lg placeholder:text-gray-300 placeholder:text-base",
props.outline ? "outline outline-2" : "",
props.errorMessage
? "outline-error-600 color-error-600"
: "outline-gray-400 focus:outline-gray-700",
props.noPadding ? "p-0" : "px-3 py-2",
]);
</script>
<div class="flex flex-col gap-2 mb-4">
<app-inputs-text-field
v-model:input="email"
label="Email"
name="email"
:error-message="errors.email"
v-bind="emailProps"
/>
<app-inputs-text-field
v-model:input="password"
label="Password"
name="password"
type="password"
:error-message="errors.password"
v-bind="passwordProps"
/>
</div>
import { useForm } from "vee-validate";
definePageMeta({
middleware: "control-access",
layout: "auth",
});
const { emailRules, passwordRules } = useFormRules();
const authStore = useAuthStore();
interface LoginForm {
email: string;
password: string;
}
const { controlledValues, handleSubmit, defineField, errors } =
useForm<LoginForm>({
validationSchema: { ...emailRules, password: passwordRules.password },
});
const [email, emailProps] = defineField("email");
const [password, passwordProps] = defineField("password");
<div class="flex flex-col gap-2 mb-4">
<app-inputs-text-field
v-model:input="email"
label="Email"
name="email"
:error-message="errors.email"
v-bind="emailProps"
/>
<app-inputs-text-field
v-model:input="password"
label="Password"
name="password"
type="password"
:error-message="errors.password"
v-bind="passwordProps"
/>
</div>
import { useForm } from "vee-validate";
definePageMeta({
middleware: "control-access",
layout: "auth",
});
const { emailRules, passwordRules } = useFormRules();
const authStore = useAuthStore();
interface LoginForm {
email: string;
password: string;
}
const { controlledValues, handleSubmit, defineField, errors } =
useForm<LoginForm>({
validationSchema: { ...emailRules, password: passwordRules.password },
});
const [email, emailProps] = defineField("email");
const [password, passwordProps] = defineField("password");
vee-validate
claims the fields are empty when I can see they are not. I reckon this is not updating the property being passed down
this is the final version, f this defineModel
bs
<template>
<div class="flex flex-col" :class="extraClasses">
<label v-if="label" class="flex mb-2 text-blue">
<p v-if="required" class="mr-1 text-xs text-error-600">*</p>
<p
class="text-sm font-semibold"
:class="[disabled ? 'text-gray-300' : 'text-gray-800']"
>
{{ label }}
</p>
</label>
<component
:is="variant ? 'input' : 'textarea'"
:id="name"
v-model="modelValue"
:name="name"
class="overflow-hidden overflow-ellipsis font-normal text-base rounded-lg placeholder:text-gray-300 placeholder:text-base"
:class="inputClasses"
:type="type"
:placeholder="placeholder ?? name"
:disabled="disabled"
:required="required"
v-bind="$attrs"
:aria-describedby="errorMessage ? `${name}-error` : null"
@input="updateValue"
/>
<span
v-if="!noErrors"
:id="`${name}-error`"
class="mt-1 text-error-500 h-3 text-[10px]"
>{{ errorMessage }}</span
>
</div>
</template>
<template>
<div class="flex flex-col" :class="extraClasses">
<label v-if="label" class="flex mb-2 text-blue">
<p v-if="required" class="mr-1 text-xs text-error-600">*</p>
<p
class="text-sm font-semibold"
:class="[disabled ? 'text-gray-300' : 'text-gray-800']"
>
{{ label }}
</p>
</label>
<component
:is="variant ? 'input' : 'textarea'"
:id="name"
v-model="modelValue"
:name="name"
class="overflow-hidden overflow-ellipsis font-normal text-base rounded-lg placeholder:text-gray-300 placeholder:text-base"
:class="inputClasses"
:type="type"
:placeholder="placeholder ?? name"
:disabled="disabled"
:required="required"
v-bind="$attrs"
:aria-describedby="errorMessage ? `${name}-error` : null"
@input="updateValue"
/>
<span
v-if="!noErrors"
:id="`${name}-error`"
class="mt-1 text-error-500 h-3 text-[10px]"
>{{ errorMessage }}</span
>
</div>
</template>
<script setup lang="ts">
export type TextFieldType = "text" | "textarea" | "number" | "password";
export interface TextFieldProps {
name: string;
placeholder?: string;
label?: string;
required?: boolean;
modelValue: string | number;
outline?: boolean;
errorMessage?: string;
type?: TextFieldType;
disabled?: boolean;
noErrors?: boolean;
extraClasses?: string;
noPadding?: boolean;
}
const props = withDefaults(defineProps<TextFieldProps>(), {
label: undefined,
schema: undefined,
errorMessage: undefined,
type: "text",
extraClasses: undefined,
placeholder: undefined,
});
const emit = defineEmits(["update:modelValue"]);
const { modelValue } = toRefs(props);
const variant = computed(() =>
["text", "number", "password"].includes(props.type)
);
const inputClasses = computed(() => [
"overflow-hidden overflow-ellipsis font-normal text-base rounded-lg placeholder:text-gray-300 placeholder:text-base",
props.outline ? "outline outline-2" : "",
props.errorMessage
? "outline-error-600 color-error-600"
: "outline-gray-400 focus:outline-gray-700",
props.noPadding ? "p-0" : "px-3 py-2",
]);
const updateValue = (event) => {
emit("update:modelValue", event.target.value);
};
</script>
<script setup lang="ts">
export type TextFieldType = "text" | "textarea" | "number" | "password";
export interface TextFieldProps {
name: string;
placeholder?: string;
label?: string;
required?: boolean;
modelValue: string | number;
outline?: boolean;
errorMessage?: string;
type?: TextFieldType;
disabled?: boolean;
noErrors?: boolean;
extraClasses?: string;
noPadding?: boolean;
}
const props = withDefaults(defineProps<TextFieldProps>(), {
label: undefined,
schema: undefined,
errorMessage: undefined,
type: "text",
extraClasses: undefined,
placeholder: undefined,
});
const emit = defineEmits(["update:modelValue"]);
const { modelValue } = toRefs(props);
const variant = computed(() =>
["text", "number", "password"].includes(props.type)
);
const inputClasses = computed(() => [
"overflow-hidden overflow-ellipsis font-normal text-base rounded-lg placeholder:text-gray-300 placeholder:text-base",
props.outline ? "outline outline-2" : "",
props.errorMessage
? "outline-error-600 color-error-600"
: "outline-gray-400 focus:outline-gray-700",
props.noPadding ? "p-0" : "px-3 py-2",
]);
const updateValue = (event) => {
emit("update:modelValue", event.target.value);
};
</script>