notmynamelastname
notmynamelastname

Reputation: 105

Limit @click event on a dynamically created element using v-for to the element it's called on

I have a component that generates a div for every element in an array using v-for. To get more info on the component you click an icon that uses fetches API data and displays it under the icon. It currently displays the fetched info under every element in the array instead of the element it's called on. How can I fix this? (new to vue.js)

Strain.vue
<template>
<div>
    <div id="strain-container" 
    v-for="(strain, index) in strains"
    :key="index"
    >
        <h3>{{ strain.name }}</h3>
        <p>{{ strain.race }}</p>
        <i class="fas fa-info-circle" @click="getDetails(strain.id)"></i>
        <strain-description :strainData="strainData"></strain-description>
    </div>
</div>
</template>

<script>
import axios from 'axios';
import strainDescription from './strainDescription'

export default {
    props: ['currentRace'],
    components: {
        'strain-description': strainDescription,
    },
    data(){
        return{
            strains: [],
            apiKey: 'removed-for-stack-overflow',
            strainData: {},
        }
    },
    methods: {
        getDetails: function(id){
            const descApi = fetch(`https://strainapi.evanbusse.com/${this.apiKey}/strains/data/desc/${id}`);
            const effectApi = fetch(`https://strainapi.evanbusse.com/${this.apiKey}/strains/data/effects/${id}`);
            const flavourApi = fetch(`https://strainapi.evanbusse.com/${this.apiKey}/strains/data/flavors/${id}`);
            axios.all([descApi, effectApi, flavourApi])
            .then((values)=> axios.all(values.map(value => value.json())))
            .then((data) => {
                this.strainData = data;
            });
        }
    },

Then output the data in strain-description component:

strainDescription.vue
<template>
    <div id="strain-description">
        <p>{{ strainData[0].desc }}</p>
        <p>{{ strainData[1] }}</p>
        <p>{{ strainData[2] }}</p>
    </div>
</template>

<script>
export default {
    props: ['strainData'],
}
</script>

Understandably (though not to me) this outputs it into every instance of the "strain-container", instead of the instance it's called on. Any help is appreciated!

Upvotes: 0

Views: 283

Answers (1)

Hides
Hides

Reputation: 546

Add the strainData to the strain in the strain array. So first you can pass the index through to your click function

<i class="fas fa-info-circle" @click="getDetails(strain.id, index)"></i>

then you can update the strains array by index with your data

getDetails: function(id, index){
            const descApi = fetch(`https://strainapi.evanbusse.com/${this.apiKey}/strains/data/desc/${id}`);
            const effectApi = fetch(`https://strainapi.evanbusse.com/${this.apiKey}/strains/data/effects/${id}`);
            const flavourApi = fetch(`https://strainapi.evanbusse.com/${this.apiKey}/strains/data/flavors/${id}`);
            axios.all([descApi, effectApi, flavourApi])
            .then((values)=> axios.all(values.map(value => value.json())))
            .then((data) => {
                this.strains[index].strainData = data;
            });
}

then back in the template you can display like so

<strain-description :strainData="strain.strainData"></strain-description>

Bonus to this is you can check whether the strainData already exists on the clicked strain by checking if strain[index].strainData is defined or not before you make an api call

EDIT

If it doesn't update the template you may need to use vue set to force the render

this.$set(this.strains[index], 'strainData', data);

Upvotes: 1

Related Questions