Search code examples
vue.jsnuxt.jsvuexvue-router

How do I avoid creating hundreds of files.vue


I am building a website with a lot of items. So the path is like:

example.com/catalog/indoor/fireplace/awesome-one

And currently I am creating hundreds and hundreds of Vue instances:

AwesomeOne.vue
AnotherOne.vue
YetAnotherThing.vue
...
AhundredthThing.vue

And all they have inside is a filtered array and passing a prop to the reusable component which uses data from Vuex storage. And so I am thinking, is there a way to avoid creating so many Vue files? I kinda sorted the problem with Nuxt.js, but it only automatically generates routes which makes the development slightly easier. But I still have to create a lot of files with nearly identical code inside (it's literally just 3 words difference: the name of the computed property, the filter option and the name of the prop).

I was thinking about some computed property which would assign an URL dynamically, using data from Vuex.state. But I can't put things together.

as requested posting the code: Catalog.vue

<template>
  <v-main>
    <v-row>
      <v-col
        v-for="category in categories"
        :key="category.i"
      >
        <v-card
          router
          :to="category.link"
        >
          <v-img
            height="250"
            width="250"
            :src="category.img"
          />
          <v-card-title>
            {{ category.name }}
          </v-card-title>
        </v-card>
      </v-col>
    </v-row>
  </v-main>
</template>

<script>
export default {
computed: {
  categories () {
    return this.$store.state.categories
  }
}
}
</script>

CatalogCategories.vue (having 5 of them)

<template>
  <v-sheet>
    <h1>Изделия для дома</h1>
    <v-row>
      <v-col
        v-for="indoorItem in indoorItems"
        :key="indoorItem.i"
      >
        <a :href="indoorItem.link">{{ indoorItem.name }}</a>
      </v-col>
    </v-row>
  </v-sheet>
</template>

<script>
export default {
  computed: {
    subCategories () {
      return this.$store.state.subCategories
    },
    indoorItems () {
      return this.subCategories.filter((category) => category.type === "indoor" || category.type === "bothdoor");
    },    
  }

}
</script>

Banquet.vue (one of the categories, having over 20 of them)

<template>
  <v-sheet>
    <h1>Банкетки</h1>
    <item-view :items="this.banquet" />
  </v-sheet>
</template>

<script>
import ItemView from "../../../components/ItemView.vue"
export default {

components: { ItemView },
computed: {
  items () {
    return this.$store.state.items
  },
  banquet () {
      return this.items.filter((item) => item.type === "banquet");
    },    
}
}
</script>

ItemView.Vue (reusable component)

<template>
  <v-sheet>
    <v-row>
      <v-col
        v-for="item in items"
        :key="item.i"
      >
        <v-card
          route
          :to="item.path"
        >
          <v-img
            width="250"
            height="250"
            :src="item.img"
          />
          <v-card-title> {{ item.name }} </v-card-title>
          <v-card-subtitle> Цена: {{ item.price }} грамм конфет му-му </v-card-subtitle>
        </v-card>
      </v-col>
    </v-row>
  </v-sheet>
</template>

<script>
export default {
    props: {
        items: {
        type: Array,
        required: true,
        },
    },
        
    }


</script>

IndividualItem.vue (I'll have to create over 100 of them)

<template>
  <v-sheet>
    <v-card>
      <individual-item-view :items="this.obossana" />
      <v-card />
    </v-card>
  </v-sheet>
</template>

<script>
import IndividualItemView from '../../../../components/IndividualItemView.vue'
export default {
  components: { IndividualItemView },
    
    computed: {
  items () {
    return this.$store.state.items
  },
  obossana () {
      return this.items.filter((item) => item.title === "obossana");
    },  

}
}
</script>

<style>

</style>

IndividualItemView.vue (reusable component)

<template>
  <v-sheet>
    <v-card
      v-for="item in items"
      :key="item.i"
    >
      <v-card-title> {{ item.name }} </v-card-title>
      
      <expandable-image
        class="image"
        close-on-background-click       
        :src="item.img"
      />
      <v-card-subtitle> Цена: {{ item.price }} раз послать тебя ко всем чертям </v-card-subtitle>
      <v-card-text> {{ item.description }} </v-card-text>
    </v-card>
  </v-sheet>
</template>

<script>

export default {
    mounted() {
    const viewportMeta = document.createElement('meta');
    viewportMeta.name = 'viewport';
    viewportMeta.content = 'width=device-width, initial-scale=1';
    document.head.appendChild(viewportMeta);
  },
props: {
        items: {
        type: Array,
        required: true,
        },
    },
}
</script>

<style scoped>
.image {
  width: 400px;
  max-width: 100%;
}
</style>

And little snippet from Vuex store:

 {
        name: "Обоссаная банкетка",
        title: "obossana",
        type: "banquet",
        path: "/catalog/indoor/banquet/obossana",
        price: "215361",
        img:
          "https://b2.3ddd.ru/media/cache/tuk_model_custom_filter_ru/model_images/0000/0000/0079/79546.53239b3804d0a.jpeg",
        description: "blah blah blah",
      },

Solution

  • Alright I figured it out.

    _view.vue for individual item view:

    <script>
    export default {
      async asyncData() {
        const items = await fetch(
          'API URL here'
        ).then((res) => res.json())
    
        return { items }
      },
      data() {
        return {
          params: this.$route.params.view,
          title: '',
        }
      },
      computed: {
        filteredItems() {
          return this.items.filter((item) => item.title === this.$route.params.view)
        },
      },
    }
    </script>
    

    turned out it was pretty easy. All I had to do is to fetch the hard coded data (I decided to use API because in the future I'll have to add more items and I believe it gonna be easier if I use API) and filter it that the item.title matches the route $route.params.view

    Inside the template tag (_view.vue file) I had:

    <template>
      <v-main>
        <h1>API: items</h1>
        <h2>'/catalog/_subcategory/_individualitem/_view.vue' here</h2>
        <h3>params.view: {{ params }}</h3>
        <v-card v-for="item in filteredItems" :key="item.i">
          <v-avatar size="200">
            <img :src="item.img" alt="картинка предмета" />
          </v-avatar>
          <v-card-title>
            {{ item.name }} and item.title: {{ item.title }}
          </v-card-title>
          <v-card-subtitle>Цена: {{ item.price }} сантиметров</v-card-subtitle>
          <v-card-text> {{ item.description }} </v-card-text>
        </v-card>
      </v-main>
    </template>
    

    h1, h2, h3 tags helped me to figure out (by displaying) values of variables.

    The file tree is (the root is catalog itself):

    ├── index.vue
    ├── _subcategory
    │   ├── _individualitem
    │   │   └── _view.vue
    │   └── _items.vue
    └── _type.vue
    

    And a little bit of api.json:

    {
        "title": "stremnaya-arka",
        "path": "/catalog/outdoor/arcs/stremnaya-arka",
        "name": "Стрёмная арка",
        "type": "arcs",    
    }
    

    Might be a bit confusing with the file names, but I hope it is not.

    I hope it'll help some other newbie who struggles as much as me ;)