Animating each individual element within the v-for loop in Vue.JS

Recently, I developed a basic to-do app using VueJS. Additionally, I integrated vue2-animate, which is a Vue.js 2.0 adaptation of Animate.css used for Vue's built-in transitions. The animation feature that adds an element functions correctly.

However, there are two issues that need addressing without unnecessary coding:

  1. The animation display for the list fetched from local storage currently runs simultaneously for all items. I require the animation to run sequentially for each item individually.
  2. There is a problem with the deletion animation - it always removes the last item first, followed by a shift in the sequence.

P.S.: To see the demo, refer to JSFiddle since local storage does not function in SO snippets.

Vue.component("adder", {
  data: function() {
    return {
      task: ""
    };
  },
  template: `
    <div class="input-group mb-3">
    <input type="text" class="form-control" placeholder="New task..." aria-label="New task..." aria-describedby="" v-model="task" v-on:keyup.enter="add">
    <div class="input-group-append">
      <button class="btn btn-primary" id="" v-on:click="add" >+</button>
    </div>
    </div>
    `,
  methods: {
    add: function() {
      this.$emit("addtodo", {
        title: this.task,
        done: false
      });
      this.task = "";
    }
  }
});

Vue.component("todo", {
  props: ["item"],
  template: `

    <a href="#" class="list-group-item list-group-item-action task" v-bind:class="{'disabled done' : item.done==true}">
    <label class="form-check-label">
            <input class="form-check-input" type="checkbox" name="" id="" value="checkedValue"  v-model="item.done"> {{item.title}}
        </label>
        <button type="button" class="close" aria-label="Close" v-on:click="del">
            <span aria-hidden="true">&times;</span>
        </button>
    </a>
    
    `,
  methods: {
    del: function() {
      this.$emit("deletetodo");
    }
  }
});

Vue.component("todos", {
  props: ["items"],
  template: `
    <div class="list-group">
        <transition-group name="bounceLeft" tag="a">
            <todo v-for="(item, index) in items" :key="index" :item.sync="item" v-on:deletetodo="delTodo(item)"></todo>
        </transition-group>
    </div>
    `,
  methods: {
    delTodo: function(i) {
      this.$emit("deletetodo", i);
    }
  }
});
Vue.config.devtools = true;

let app = new Vue({
  el: ".todoapp",
  data: {
    title: "To-Do App",
    items: []
  },
  methods: {
    addTodo: function(e) {
      this.items.push(e);
    },
    delTodo: function(i) {
      this.items = this.items.filter(e => e != i);
    }
  },
  mounted() {
    if (localStorage.items) {
      this.items = JSON.parse(localStorage.getItem("items"));
    }
  },
  watch: {
    items: {
      handler(val) {
        localStorage.setItem("items", JSON.stringify(this.items));
      },
      deep: true
    }
  }
});
.done>label {
  text-decoration: line-through;
}

.task {
  padding-left: 36px;
}
<!DOCTYPE html>
<html lang="en">

<head>
  <title>To-Do App</title>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
  <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous" />

  <link rel="stylesheet" href="https://unpkg.com/vue2-animate/dist/vue2-animate.min.css" />
  <link rel="stylesheet" href="style.css" />
</head>

<body>
  <div class="container todoapp">
    <div class="row">
      <br />
    </div>
    <div class="card">
      <div class="card-header">
        {{ title }}
      </div>
      <div class="card-body">
        <adder v-on:addtodo="addTodo"></adder>
        <todos :items.sync="items" v-on:deletetodo="delTodo"></todos>
      </div>
    </div>
  </div>
  <script src="https://cdn.jsdelivr.net/npm/vue"></script>
  <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js" integrity="sha384-ZMP7rVo3mIykV+2+9J3UJ46jBk0WLaUAdn689aCwoqbBJiSnjAK/l8WvCWPIPm49" crossorigin="anonymous"></script>
  <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js" integrity="sha384-ChfqqxuZUCnJSK3+MXmPNIyE6ZbWh2IMqE241rYiqJxyMiZ6OW/JmZQ5stwEULTy" crossorigin="anonymous"></script>
  <script src="script.js"></script>
</body>

</html>

Check out the JSFiddle demo here

Answer №1

Let's tackle this step by step:

Removing a task

The reason why it always appears that the last task is being deleted is because you are using the index as the key for your list items. When you replace the entire items array in your delTodo method, a new array with new keys for each item is generated. If you key by item, then you will get the intended result:

<todo v-for="(item, index) in items" :key="item" :item.sync="item" v-on:deletetodo="delTodo(item)"></todo>

Displaying tasks one at a time upon loading

I suggest handling the showing/hiding of tasks with a computed property:

computed: {
    tasks: function(){
        return this.items.filter(item => item.isVisible);
    }
}

This approach involves toggling the value of isVisible on each task for show/hide functionality.

For example, when initially loading tasks from local storage, you could set all tasks to isVisible: false. Then, utilize a setTimeout within a for loop to display them one by one:

mounted() {
    // Retrieve items and initialize all as hidden
    if (localStorage.items) {
        this.items = JSON.parse(localStorage.getItem("items"))
                     .map(item => item.isVisible = false);
    }

    // Iterate through to reveal the tasks
    for(let i=1; i<=this.items.length; i++){
        // Define 300 milliseconds delay
        let delay = i * 300;

        setTimeout(function(){
            this.items[i].isVisible = true;
        }.bind(this), delay);
    }
},

Answer №2

One great success we experienced was the phased addition of items to the array:

  mounted() {
    let items = [];
    if (localStorage.items) {
      items = JSON.parse(localStorage.getItem("items"))
    }
    for (let i = 0; i < items.length; i++) {
      let delay = i * 1000;
      setTimeout(
        function() {
          this.items.push(items[i])
        }.bind(this),
        delay
      )
    }
  }

Answer №3

Adding onto the discussion, here is a method to achieve staggering within a Vuex Action and utilizing fat arrow syntax:

async fetchRepositories( {commit} ){
  const response = await gitHubApi.get<Repository[]>('/users/rodolphocastro/repos') // Using Axios to call API
  const staggered: Repository[] = []
  response.data.forEach((r, i) => {
    const delay = i * 300 // 300ms -> Time interval for each item in the array
    setTimeout(() => {
      staggered.push(r)
      commit('setRepositories', staggered)
    }, delay)
  })
}

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

Calculating Time Difference in JavaScript Without Utilizing moment.js Library

I have two timestamps that I need to calculate the difference for. var startTimestamp = 1488021704531; var endTimestamp = 1488022516572; I am looking to find the time difference between these two timestamps in hours and minutes using JavaScript, without ...

The ajax function does not provide a response

Can you help me figure out why this JavaScript function keeps returning 'undefined'? I really need it to return either true or false. I've included my code below: function Ajax() { var XML; if(window.XMLHttpRequest) XML=new ...

Textbox value disappears after being updated

When I click on the edit link (name), the value from the database is displayed as a textbox. However, when I update another normal textbox (age), the value in the edit link textbox disappears. Strangely, if I input a new value into the edit link textbox, i ...

Strings that automatically adjust to fit the content from a JSON object

I have a JSON object with a long string that I want to display. The string looks like this: "String":"<p>Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever si ...

Unable to extract all advertisements from Facebook Marketplace

https://i.stack.imgur.com/xEhsS.jpg I'm currently attempting to scrape listings from Facebook marketplace, however, only the first listing is being scraped. Does anyone have any suggestions on how I can scrape the entire list of listings? CODE (async ...

Function that observes with the pipe syntax

Is it possible to apply map, switchMap, or any other operator in conjunction with a function that outputs an observable? The objective here is to transform the result of the observable function and utilize that value during the subscription to another ob ...

Encountering a registration error persistently: [TypeError: Network request failed]

Currently, I am in the process of developing an application using React for the frontend and node.js for the backend. However, I have encountered a persistent network error whenever I try to sign up or log in. What puzzles me is that when I test the API en ...

What is the best approach to dynamically implement useReducer in code?

Take a look at the repository here: https://github.com/charles7771/ugly-code In the 'Options' component, I am facing an issue where I am hardcoding different names for each reducer case instead of dynamically generating them for a form. This app ...

The failure of a unit test involving Jest and try-catch

I am facing an issue with creating unit tests using jest. exportMyData.js const createJobTrasferSetArray = async () => { const data = exportData(); const jobTransferSet = []; try { data.forEach((element) => { const JobPathArray = el ...

What are the different ways to share data between components in React?

There are two components in my code, one named <Add /> and the other named <Modal>. The <Add /> component has a state for handling click events. Whenever the <Add /> component is clicked, the state is set to true. I need to utilize ...

Ways to transfer information from a function within one element to another element

I have two components: one that logs the indexes of a carousel and I need to pass these indexes into the second component. First Component <template> <div class="container container--white"> <Header /> <carousel-3d @a ...

Internet Explorer automatically moves the cursor to the beginning of a textarea when it gains

When trying to insert "- " into an empty textarea, I am facing issues with Internet Explorer. While Firefox and Chrome work perfectly fine by inserting the text as expected, IE causes it to jump to the beginning of the textarea after insertion. Therefore, ...

Creating a Triangle Slider Component with React and Material-UI (MUI)

Struggling with creating a price control using react js and MUI, similar to the image below: https://i.stack.imgur.com/ZP8oc.png I've searched online for libraries or solutions, but haven't found anything that works. ...

Is there a way to prevent an iframe from being recorded in Chrome's browsing history?

I'm in the process of developing a Chrome extension that inserts an Angular application into the user's browser window to create an interactive sidebar. I've been successful in most of my goals by inserting an iframe through a content script ...

Exploring the world of JMeter: capturing sessions with JavaScript and jQuery

I need to capture a user session in order to conduct a performance test. I have been using the JMeter HTTP(S) Test Script Recorder, but unfortunately it is not recognizing javascript and jquery. The error message I'm receiving is: JQuery is not def ...

Ways to bypass mongoose schema validation while making an update request in the API

In my model, one of the fields is specified as providerID: { type: Number, required: true, unique: true }. The providerID is a unique number that is assigned when inserting provider details for the first time. There are situations where I need to update ...

Get the Zip file content using PushStreamContent JavaScript

I am looking for the correct method to download a PushStreamContent within a Post request. I have already set up the backend request like this: private static HttpClient Client { get; } = new HttpClient(); public HttpResponseMessage Get() { var filenames ...

The dynamic routing feature in React fails to function properly after the application is built or deployed

I'm facing an issue with getting a React route to function properly in the build version of my app or when deployed on Render. Here are the routes I have: <Route path="/" element={userID ? <Home /> : <Login />} /> <Route ...

Angular Oops! We ran into a small hiccup: [$injector:modulerr]

I am facing an issue with an angular js error angular.js:36 Uncaught Error: [$injector:modulerr] http://errors.angularjs.org/1.2.19/$injector/modulerr?p0=app&p1=Error%3A%20…gleapis.com%2Fajax%2Flibs%2Fangularjs%2F1.2.19%2Fangular.min.js%3A18%3A139) ...

Why is the image cut in half on mobile devices but displaying correctly on computer screens? Could it be an issue with

There seems to be an issue on mobile screens that does not occur on computer screens. When the user clicks on the image, it disappears, and when they click another button, it reappears. However, there is a problem with how the image appears - it is cut off ...