Unusual "visual" phenomenon with autocomplete feature in VUE.js

Can someone review this code snippet?

Check out the code here

This is a peculiar example of a custom autocomplete VUE component.

If you enter a value in one of the fields in Section 1 (like 'Apple'), then click on the Next button, you'll notice that the same value appears incorrectly in some of the fields in Section 2 (still 'Apple'). However, if you click on Display vals, it will correctly display all values from both sections.

Many thanks in advance!

HTML:

<div id="app">

  <div v-if="section == 1">
    <p>Section {{section}}</p>
    <br>
    <div>
      <label>Test1</label>                          
      <autocomplete v-model="test1" 
                         :items="theItems">
      </autocomplete>
    </div>
    <br>
    <div>   
      <label>Test2</label>                          
      <autocomplete v-model="test2" 
                         :items="theItems">
      </autocomplete>
    </div>
  </div>

  <div v-if="section == 2">
    <p>Section {{section}}</p>
    <br>
    <div>
      <label>Test3</label>                          
      <autocomplete v-model="test3" 
                         :items="theItems">
      </autocomplete>
    </div>
    <br>
    <div>   
      <label>Test4</label>                          
      <autocomplete v-model="test4" 
                         :items="theItems">
      </autocomplete>
    </div>
  </div>

  <br>

<button v-if="section == 2" type="button" v-on:click="section=1">Prev</button>
<button type="button" v-on:click="displayVals()">Display vals</button>
<button v-if="section == 1" type="button" v-on:click="section=2">Next</button>

</div>



<script type="text/x-template" id="autocomplete">
  <div class="autocomplete">
    <input type="text" @input="onChange" v-model="search" @keyup.down="onArrowDown" @keyup.up="onArrowUp" @keyup.enter="onEnter" />
    <ul id="autocomplete-results" v-show="isOpen" class="autocomplete-results">
      <li class="loading" v-if="isLoading">
        Loading results...
      </li>
      <li v-else v-for="(result, i) in results" :key="i" @click="setResult(result)" class="autocomplete-result" :class="{ 'is-active': i === arrowCounter }">
        {{ result }}
      </li>
    </ul>

  </div>
</script>

VUE:

const Autocomplete = {
  name: "autocomplete",
  template: "#autocomplete",
  props: {
    items: {
      type: Array,
      required: false,
      default: () => []
    },
    isAsync: {
      type: Boolean,
      required: false,
      default: false
    }
  },

  data() {
    return {
      isOpen: false,
      results: [],
      search: "",
      isLoading: false,
      arrowCounter: 0
    };
  },

  methods: {
    onChange() {
      // Notify parent of change
this.$emit("input", this.search);
      // Handle async vs. local data
      if (this.isAsync) {
        this.isLoading = true;
      } else {
        // Search local array
        this.filterResults();
        this.isOpen = true;
      }
    },

    filterResults() {
      // Filter results based on input
      this.results = this.items.filter(item => {
        return item.toLowerCase().indexOf(this.search.toLowerCase()) > -1;
      });
    },
    setResult(result) {    
      this.search = result;
            this.$emit("input", this.search);
      this.isOpen = false;
    },
    onArrowDown(evt) {
      if (this.arrowCounter < this.results.length) {
        this.arrowCounter = this.arrowCounter + 1;
      }
    },
    onArrowUp() {
      if (this.arrowCounter > 0) {
        this.arrowCounter = this.arrowCounter - 1;
      }
    },
    onEnter() {
      this.search = this.results[this.arrowCounter];
      this.isOpen = false;
      this.arrowCounter = -1;
    },
    handleClickOutside(evt) {
      if (!this.$el.contains(evt.target)) {
        this.isOpen = false;
        this.arrowCounter = -1;
      }
    }
  },
  watch: {
    items: function(val, oldValue) {
      // Check for changes in items
      if (val.length !== oldValue.length) {
        this.results = val;
        this.isLoading = false;
      }
    }
  },
  mounted() {
    document.addEventListener("click", this.handleClickOutside);
  },
  destroyed() {
    document.removeEventListener("click", this.handleClickOutside);
  }
};

new Vue({
  el: "#app",
  name: "app",
  components: {
    autocomplete: Autocomplete
  },
  methods: {
    displayVals() {
        alert("test1=" + this.test1 + ", test2=" + this.test2 + ", test3=" + this.test3 + ", test4=" + this.test4);
    },
  },
  data: {
    test1: '',
        test2: '',
        test3: '',
        test4: '',
        section: 1,
        theItems: [ 'Apple', 'Banana', 'Orange', 'Mango', 'Pear', 'Peach', 'Grape', 'Tangerine', 'Pineapple']
  }
});

CSS:

#app {
  font-family: "Avenir", Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  color: #2c3e50;
  margin-top: 60px;
}

.autocomplete {
  position: relative;
  width: 130px;
}

.autocomplete-results {
  padding: 0;
  margin: 0;
  border: 1px solid #eeeeee;
  height: 120px;
  overflow: auto;
  width: 100%;
}

.autocomplete-result {
  list-style: none;
  text-align: left;
  padding: 4px 2px;
  cursor: pointer;
}

.autocomplete-result.is-active,
.autocomplete-result:hover {
  background-color: #4aae9b;
  color: white;
}

Thank you once more!

Answer №1

It seems that the issue arises when utilizing the v-if binding for toggling, causing your <autocomplete> component to be destroyed and losing its state in the process. However, your data remains intact as they are reactive and updated when displayed in the DOM (e.g. using {{ test1 }} in your app template).

To resolve this issue, you can wrap your component inside a <keep-alive> tag and utilize the v-bind:key attribute, like so:

<keep-alive>
    <autocomplete v-model="test1" v-bind:key="1" :items="theItems">
    </autocomplete>
</keep-alive>

You can view a proof-of-concept of this solution in this updated fiddle: https://jsfiddle.net/teddyrised/t0ey81jw/9/

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 PHP code embedded within the HTML document and triggered by an AJAX request failed to

Here is an example of my ajax call: function insertModal(link) { $.ajax({ url: link, cache: false, dataType: "text", success: function(data) { $("#modalInput").html(data); }, error: function (request, status, error) { ...

I created a custom discord.js-commando command to announce all the channels that my bot is currently active in, however, encountered an unexpected error

const Commando = require('discord.js-commando'); module.exports = class AnnounceCommand extends Commando.Command { constructor(client) { super(client, { name: 'announce', aliases: ['an'], ...

Display the div only after all images have finished loading in response to the AJAX call

Prior to diving into this, I am determined to steer clear of utilizing Jquery for personal reasons that I won't delve into. So please refrain from suggesting it, as that is not the solution I seek. I am currently working on a web page that sends mult ...

Guide on how to switch a class on the body using React's onClick event

There's a button in my code that triggers the display of a modal-like div element. When this button is clicked, I aim to apply a class to the body element; then when the close button is clicked, I'll remove this class. I'm looking for guid ...

Tips for extracting variables from a querystring in Express?

I am trying to retrieve values sent to the server: "/stuff?a=a&b=b&c=c" Can you please advise me on how to extract these values using express? So far, I have attempted... app.get( "/stuff?:a&:b&:c", function( req, res ){}); ...but unfo ...

Having difficulties with implementing the throw await syntax in an async express handler in Node.js

const express = require("express"); const expressAsyncHandler = require("express-async-handler"); const app = express(); const func = async () => { return false; }; app.get( "/", expressAsyncHandler(async () => ...

What is the proper way to incorporate a ref within a class component?

I am encountering an issue with my class component. I'm wondering if there is a comparable feature to useRef() in class components? Despite several attempts at researching, I have yet to find a solution. ...

Leveraging vuetify to customize layouts based on varying screen dimensions

I am currently working on a Vue.js application and utilizing Vuetify. My table layout initially appears as shown below: https://i.stack.imgur.com/EocMn.png However, when I reduce the viewport width, the layout ends up looking like this: https://i.stack. ...

The inline variables in CSS transitions are failing to be detected

The CSS transitions are not recognizing the inline variables, or if they are, they are not receiving the correct information. Below is an illustration of how the inline variables are defined in the HTML: <p class="hidden" style="--order ...

Vuex was unable to locate the required dependency

Currently, I'm following an instructional video that incorporates Vuex. As shown in my package.json dependencies, I have installed Vuex: { "name": "blabla", "version": "1.0.0", "description": "blablaa", "author": "blabla", "private": true, ...

Is it possible to send an AJAX request to a Django view that might result in a redirect?

Query I encountered an issue while attempting to access a specific Django view through AJAX. This particular view redirects users if they haven't authorized the site with Google. I suspect the problem arises from redirecting "within" a view requested ...

Obtaining the initial row information from jqGrid

If I use the getRowData method, I can retrieve the current cell content instead of the original data before it was formatted. Is there a way to access the original content before any formatting transformations are applied? Just so you know, I am filling t ...

Exploring the assortment of reactions post-awaitReaction in node.js

The current code runs smoothly, but I encounter an issue when attempting to send messages after selecting either the X or check option. Instead of the expected outcome, I receive Despite my understanding that this collection is a map, all attempts to acce ...

Tips on utilizing recursive Promises within a loop while transferring arguments to another function

Despite searching other threads on SO, I couldn't find a satisfactory answer to my problem. Every solution seems to be missing something essential for my case, especially since none of them address Apple Music specifically. The Apple API relies heavil ...

Ways to modify the attribute of an element in an ImmutableList({}) nested within Immutable.Map({})

Is there a way to modify the property of an item within an ImmutableList({}) that is nested inside an Immutable.Map({})? This is my current setup: const initialState = Immutable.Map({ width: window.board.width, height: window.board.height, li ...

Does creating a form render the "action" attribute insignificant in an AJAX environment?

When submitting forms exclusively through AJAX, is there any advantage to setting the action attribute at all? I have yet to come across any AJAX-form guides suggesting that it can be left out, but I fail to see the purpose of including it, so I wanted t ...

Exploring the Response Object in Express JS: A Comprehensive Guide

I'm currently using Express version 4.13.4. My goal is to access and examine the res object. However, when I try to use console.log() on it, the output is too lengthy to display properly in a command window. I attempted to utilize jsonfile to save i ...

Using Vuexfire to bind Firebase references with pagination and infinite scrolling

Query: How do I implement pagination (infinite scroll) for my bound Firestore VuexFire reference without re-querying previously fetched (and bound) data? Context: I am currently using VuexFire firestore binding to populate a timeline with the most upvoted ...

Error handling in Mongoose callback functions

Currently, I am delving into nodejs, express and mongoose. A question has arisen in my mind regarding the findOne function used to fetch a document from the database. Typically, it is utilized like this: Product.findOne({_id: req.params.id},function(erro ...

The information returned to the callback function in Angular comes back null

In my Node.js application, I have set up an endpoint like this: usersRoute.get('/get', function(req, res) { //If no date was passed in - just use today's date var date = req.query.date || dateFormat(new Date(), 'yyyy-mm-dd&ap ...