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
:export default { props: { text: {/*...*/}, }, data() { return { value: '' } }, watch: { text(newText) { this.value = newText } }, }
Use the new
value
property as the<input>
‘sv-model
:<input v-model="value">
Remove the
keyup
handler and thecharacterCount
data property, and instead use a computed prop that returns the length ofvalue
:export default { computed: { characterCount() { return this.value.length } }, }