Toggle button not working properly in Vue

Toggle button not working properly in Vue

Problem Description:

I’m building my first Vue app. I have some experience with React, and I’m trying to make a simple movies list in Vue.

There are two bugs in my app – both related to the button toggling:

  1. OnClick I want to change hideImg, and display the other image. I can only toggle it once. It refuses to return back to neutral state.

  2. OnClick I want to toggle show the video. It works properly – opens a video on button click, and then if I click another button it closes the old video and shows new one. But once I open all 4 videos, I cannot show or close any more videos.

It seems like I can only toggle each element ONCE.

Am I missing something?

<template>
  <div v-for="(name, index) in movies" :key="index" class="movieContainer">
    <div class="center">
      {{ name.name }} {{ name.duration }}
      <button
        @click="name.hideImg = !name.hideImg && toggle(name.id)"
        class="watchBtn"
      >
        <p v-if="name.hideImg">►</p>
        <p v-else>▼</p>
      </button>
    </div>
    <div v-show="name.id == open">
      <video controls="controls" autoplay name="media">
        <source
          :src="require(`@/assets/movie${index + 1}.mp4`)"
          alt="video"
          type="video/mp4"
          width="500px"
        />
      </video>
    </div>
  </div>
</template>

<script>
export default {
  name: "App",
  methods: {
    toggle(id) {
      this.open = this.open === id ? null : id;
    },
  },
  data() {
    return {
      movies: [
        {
          name: "Pokemon",
          duration: "1hr 12min",
          hideImg: true,
          open: null,
          id: 1,
        },
        {
          name: "Digimon",
          duration: "2hr 37min",
          hideImg: true,
          open: null,
          id: 2,
        },
        {
          name: "Transformers",
          duration: "1hr 51min",
          hideImg: true,
          open: null,
          id: 3,
        },
        {
          name: "Kiepscy",
          duration: "1hr 51min",
          hideImg: true,
          open: null,
          id: 4,
        },
      ],
    };
  },
};
</script>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin: 60px auto 0;
}
.watchBtn {
  background-color: red;
  border-radius: 10px;
  margin-left: 10px;
  height: 20px;
  display: flex;
  align-items: center;
}
.watchBtn:hover {
  background-color: rgb(255, 191, 107);
  cursor: pointer;
}
.movieContainer {
  margin: 5px auto;
  display: flex;
  flex-direction: column;
}
.center {
  margin: 0 auto;
  display: flex;
}
.movieContainer video {
  width: 500px;
}
</style>

Solution – 1

There are a couple quick things that may help. Your toggle() could be cleaned up a bit. You can do both of those actions in the toggle() method.

And you’re accessing the video incorrectly. this.open is looking for a data property named open at the top level of your data(), and it’s not finding it. So you need to drill down into movies to get the right property.

Also, your indices for the movies start at 1, but the loop is starting at 0, so things are off by 1.

I put together a StackBlitz so you can see what I’m talking about.

Here’s the code for posterity:

<template>
  <div v-for="(movie, index) in movies" :key="index" class="movieContainer">
    <div class="center">
      {{ movie.name }}
      <button
        @click="toggle(movie.id)"
        class="watchBtn"
      >
        <p v-if="movie.hideImg">►</p>
        <p v-else>▼</p>
      </button>
    </div>
    <div v-show="movie.open">
      <!-- <video controls="controls" autoplay name="media">
        <source
          :src="require(`@/assets/movie${index + 1}.mp4`)"
          alt="video"
          type="video/mp4"
          width="500px"
        />
      </video> -->
      <h2>It's Working!</h2>
    </div>
  </div>
</template>

<script>
export default {
  name: "App",
  methods: {
    toggle(id) {
      this.movies[id - 1].hideImg = !this.movies[id - 1].hideImg
      this.movies[id - 1].open = !this.movies[id - 1].open
    },
  },
  data() {
    return {
      movies: [
        {
          name: "Pokemon",
          duration: "1hr 12min",
          hideImg: true,
          open: false,
          id: 1,
        },
        {
          name: "Digimon",
          duration: "2hr 37min",
          hideImg: true,
          open: false,
          id: 2,
        },
        {
          name: "Transformers",
          duration: "1hr 51min",
          hideImg: true,
          open: false,
          id: 3,
        },
        {
          name: "Kiepscy",
          duration: "1hr 51min",
          hideImg: true,
          open: false,
          id: 4,
        },
      ],
    };
  },
};
</script>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin: 60px auto 0;
}
.watchBtn {
  background-color: red;
  border-radius: 10px;
  margin-left: 10px;
  height: 20px;
  display: flex;
  align-items: center;
}
.watchBtn:hover {
  background-color: rgb(255, 191, 107);
  cursor: pointer;
}
.movieContainer {
  margin: 5px auto;
  display: flex;
  flex-direction: column;
}
.center {
  margin: 0 auto;
  display: flex;
}
.movieContainer video {
  width: 500px;
}
</style>

I commented out the video for testing because I don’t have it locally. But I think with this the toggle buttons work as expected.

Rate this post
We use cookies in order to give you the best possible experience on our website. By continuing to use this site, you agree to our use of cookies.
Accept
Reject