Using @keyup/@input events on Vue data property causes issues with form inputs

I'm having an issue with attaching a character counter to an input element. After displaying it back to the user, the input stops allowing me to enter more characters into 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>

In summary of the code above, I am implementing basic cleansing on change and attempting to activate a character count on key up. What could be causing the issue I'm facing?

Answer №1

The update of characterCount within the keyup event handler is causing a complete re-rendering of the component to display the updated value of the characterCount within the template. This re-rendering involves the <input> element, which is bound to the text property. When text is empty or null, the <input> effectively gets cleared during the keyup event.

To fix this issue, a local copy of the text prop should be created and bound to the <input>'s v-model.

  1. Start by creating a data property (named "value") and a watcher on the text prop to copy its value into value:

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

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

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

https://codesandbox.io/s/troubleshooting-input-changes-x0jtc?fontsize=14&hidenavigation=1&theme=dark

Answer №2

To achieve two-way data binding, replace :value with v-model and create a local property (e.g. localText) instead of directly mutating the text prop. Utilize a computed property for calculating the length for enhanced readability. Here is a modified version:

<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" v-model="localText" @change="formatValue($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,
            default: 10,
            type: Number
        },
        placeholder: {
            required: false,
            type: String
        },
        text: {
            required: false,
            type: String
        },
        validationMessage: {
            default: "Required field.",
            required: false,
            type: String
        }
    },
    data() {
        return {
            localText: this.text || '',
            validationFailed: false,
            value: undefined
        }
    },
    computed: {
        characterCount() {
            return this.localText.length;
        },
        characterCountWarningStyle() {
            return "" // Simplified.
        },
        placeholderText() {
            return "" // Simplified.
        }
    },
    methods: {
        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>

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

The functionality of Ajax loading content will fail when the link is already loaded

I am currently using an ajax script that dynamically replaces the content of #main with the contents of a loaded page based on the clicked link. For example, clicking on rddd would replace #main with the content of about.php. However, I encountered an is ...

Utilizing JavaScript or jQuery to adjust the cursor pointer value based on user input

Issue: Need help with live updating of input value to range input pointer [Check out my CodePen for reference][1] Adjust upper range input pointer based on lower range input pointer ][2] I am currently working on a range-to-range calculator for a book ...

Using Angular to send JSON data to an API

I'm attempting to send JSON data to an API, but I'm encountering the following error... 405 (Method Not Allowed) Below is the JSON object I'm trying to send and the corresponding HTTP request... var productAttributes = { "CostRequire ...

Failure to receive results from $.getJSON request

I am currently working on developing a web page that allows users to search and retrieve Wikipedia pages based on their search results. Below is the relevant HTML code for the search feature: <form class='search-form' onsubmit='return f ...

Utilizing AngularJs to connect server-generated HTML content to an iframe

My Angular app functions as an HTML editor that transmits the template to a server for rendering with dynamic data. The rendered content is then sent back to the client, where it needs to be placed inside an iframe for preview purposes. It appears that ng- ...

individual elements displayed sequentially within the same block with variable naming

My code caters to both mobile and desktop versions, with the same elements in the FORM. However, only one block of code is visible at a time depending on the screen size, with one set to display: none. <div id="desktop"> <div id="content"> ...

Using a GET request in JavaScript to access a RESTful web service

I'm having trouble making a GET request to my web service. My web service is configured with x and y values, where x ranges from A-E and y ranges from 1-5. For example, let's say the values are set to C1. When I try to access the URL: x.x.x.x:x ...

nextJS does not recognize the term 'Window'

I'm encountering the error "window is not defined" in my NextJS project. The variable isMobile determines whether the window size is less than 767.98 to handle the hamburger menu functionality. This code worked in ReactJS but seems to be causing issue ...

AngularJS handles intricate JSON responses effortlessly

I have been learning angularjs on my own, mostly from w3schools and other websites. I have been trying to download and parse some JSON data. I was successful with a simple JSON url (http://ip.jsontest.com), but I am struggling with a more complex response. ...

execution won't be halted by return statement within forEach in JavaScript

const checkAttendanceStudent = (attendance, std_id) => { //check if the teacher made attendance for this current session let checkNow = attendance.find((element) => { if (checkDateAttendance(element.date)) { //check if the s ...

Issue with Vue JS function not providing the desired array output

I declared a property in my data model like this: someArray: [] A function returns an array: getMyArray: function (someId) { var result = [7, 8, 9, 10]; return result; } I'm assigning the result of the function to m ...

Clickability issue in Angular's ui-router: Non-responsive Links

I am trying to implement angular-ui-router to manage my views, but I am encountering an issue. The two links in the view below are not responsive when clicked. Even though Angular changes the variable with the link label, I am unable to interact with them. ...

When using Laravel with Vue and Axios, a peculiar issue arises where a DELETE request sent using

I'm encountering a specific error and I'm struggling to identify the root cause. Instead of providing more explanations, let me show you with code: These are my defined routes : Route::get('/', 'HomeController@index')->n ...

Sending data using jQuery to a web API

One thing on my mind: 1. Is it necessary for the names to match when transmitting data from client to my webapi controller? In case my model is structured like this: public class Donation { public string DonorType { get; set; } //etc } But the f ...

Identify all td inputs within a TR element using jQuery

Is there a way to retrieve all the input values within each table cell (td) of a table row (tr) using jQuery? Suppose I have a tr with multiple td elements, and some of these tds contain inputs or select elements. How can I extract the values from these in ...

Dragend event for images does not trigger in webkit when using TinyMCE

When using the TinyMCE editor, I tried binding the dragend event on images with the following code: _imagePlugin.editor.dom.bind(_imagePlugin.editor.dom.select('img'), 'dragend', function(){console.log('aaaa');}); Oddly enou ...

Tips for including information in a chart

I'm facing an issue with my current code as it is not functioning properly. Here is the link to the current output: http://jsfiddle.net/4GP2h/50/ ...

Can you guide me on setting a background image URL for rails using javascript?

Within my Rails application, I have a collection of content paired with image buttons (each piece of content has an associated image). When a user clicks on one of these buttons, my goal is to display the corresponding image. All of my images are stored u ...

The history mode router in Vue disrupts the dynamic URL hashing

When using Vue version 2.6.14 and setting Vue Router to history mode, encountering a URL with a hashtag "#" can cause issues with dynamic paths. const router = new VueRouter({ base: `${process.env.VUE_APP_PUBLIC_PATH}`, mode: 'history', rou ...

Animating with JavaScript

I have a members button on my website that triggers a dropdown animation when clicked. However, the issue is that the animation occurs every time a page is loaded, even if the button is not clicked. I want the dropdown to only open when the button is click ...