Skip to content
Advertisement

Vue: attaching @keyup/@input event to data property breaks form input

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.

  1. Create a data property (named "value"), and a watcher on the text prop that copies text into value:

    export default {
      props: {
        text: {/*...*/},
      },
      data() {
        return {
          value: ''
        }
      },
      watch: {
        text(newText) {
          this.value = newText
        }
      },
    }
    
  2. Use the new value property as the <input>‘s v-model:

    <input v-model="value">
    
  3. Remove the keyup handler and the characterCount data property, and instead use a computed prop that returns the length of value:

    export default {
      computed: {
        characterCount() {
          return this.value.length
        }
      },
    }
    

Edit Troubleshooting input changes

User contributions licensed under: CC BY-SA
4 People found this is helpful
Advertisement