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 thetext
prop that copiestext
intovalue
:JavaScript116161export default {
2props: {
3text: {/*...*/},
4},
5data() {
6return {
7value: ''
8}
9},
10watch: {
11text(newText) {
12this.value = newText
13}
14},
15}
16
Use the new
value
property as the<input>
‘sv-model
:JavaScript121<input v-model="value">
2
Remove the
keyup
handler and thecharacterCount
data property, and instead use a computed prop that returns the length ofvalue
:JavaScript181export default {
2computed: {
3characterCount() {
4return this.value.length
5}
6},
7}
8