I’m trying to attach a simple character counter to an input element but the second I display it back to the user, the input breaks in that I’m unable to enter any additional characters in the input box.
<template>
<div>
<label class="label" :class="{ 'label-large' : large }" v-if="label">
{{ label }} <sup class="is-required" v-if="isRequired">Req</sup>
</label>
<input class="input-control" :class="{ 'input-large' : large }" :maxlength="maxLength" :placeholder="placeholderText" ref="input" :value="text" @change="formatValue($event.target.value)" @keyup="countCharacters($event.target.value)" />
<div class="flex text-x-small-regular mt-2" :class="large ? 'px-4' : 'px-2'" v-if="maxLength || validationFailed">
<div class="validation-message">
<template v-if="validationFailed">{{ validationMessage }}</template>
</div>
<div class="character-count" v-if="maxLength">
<span :class="characterCountWarningStyle">{{ characterCount }}</span> / {{ maxLength }}
</div>
</div>
</div>
</template>
<script>
export default {
props: {
isRequired: {
default: false,
required: false,
type: Boolean
},
label: {
required: false,
type: String
},
large: {
default: false,
required: false,
type: Boolean,
},
maxLength: {
required: false,
type: Number
},
placeholder: {
required: false,
type: String
},
text: {
required: false,
type: String
},
validationMessage: {
default: "Required field.",
required: false,
type: String
}
},
data() {
return {
characterCount: 0,
validationFailed: false,
value: undefined
}
},
computed: {
characterCountWarningStyle() {
return "" // Simplified.
},
placeholderText() {
return "" // Simplified.
}
},
methods: {
countCharacters(value) {
// Works:
console.log(value.length);
// Breaks form input: this.characterCount = value.length;
},
formatValue(value) {
this.validationFailed = false;
if (value) value = value.trim();
this.validate(value);
},
validate(value) {
if (this.isRequired && !value) {
this.validationFailed = true;
}
this.$emit('update', value);
}
}
}
</script>
To summarize the code above, I’m doing some basic cleansing on change, and am looking to trigger a character count on key up. What am I missing?
Advertisement
Answer
The characterCount update in the keyup handler is triggering a rerender of the entire component in order to render the new value of the characterCount string interpolation in the template. The rendering includes the <input>, whose value is bound to text. If text is an empty string or null, the <input> is effectively cleared on keyup.
To resolve the issue, use a local copy of the text prop that can be modified, and bind it to the <input>‘s v-model.
Create a data property (named
"value"), and a watcher on thetextprop that copiestextintovalue:export default { props: { text: {/*...*/}, }, data() { return { value: '' } }, watch: { text(newText) { this.value = newText } }, }Use the new
valueproperty as the<input>‘sv-model:<input v-model="value">
Remove the
keyuphandler and thecharacterCountdata property, and instead use a computed prop that returns the length ofvalue:export default { computed: { characterCount() { return this.value.length } }, }