Search code examples
vue.jsvue-componentvuejs3

Vue 3: Toggle class onclick of button inside v-for loop


I have my vue 3 app and it almost works. Inside there's a v-for looping over an array of items. This ends up in a fine list for each item in the array. On each item in the list there's some partially hidden text and a button for showing all the text based on click of that button. The text is fully shown or partially hidden based on a css class on the element.

I have made it work onclick of a button, but this ends up showing the text for all elements or none of the elemts, as it's the global property that is changed. For that to work my code looks like this:

<script>
const isActive = ref(false);
const toggleAccordion = async () => {
    isActive.value = !isActive.value;

    console.log(isActive.value); //This one changes between false and true correctly
};
</script>

<template>
    <div>
        <div v-for="item in items" :key="item.id">
            <div class="my-class" :class="{ 'is-open': isActive }" v-html="item.details"></div>
        </div>
        <button @click="() => toggleAccordion()">Toggle text</button>
    </div>
</template>

As mentioned, the above code toggles the class for all the elements in the loop.

So I tried out some different stuff, and I thought that I could make it work with the index in my function and the loop. My one of my attempts I tried the following code:

<script>
    const isActive = ref(false);
    const toggleAccordion = async (i) => {
        isActive[i].value = !isActive[i].value;        
    };
</script>
    
<template>
    <div>
        <div v-for="item in items" :key="item.id">
            <div class="my-class" :class="{ 'is-open': isActive[i] }" v-html="item.details"></div>
        </div>
        <button @click="() => toggleAccordion(i)">Toggle text</button>
    </div>
</template>

But I must have missed something somewhere, as it does not and I get console error saying the following:

Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'value')

The console error goes for this line: isActive[i].value = !isActive[i].value;

I also tried a similar approach, which does not give any erros in the console. This one:

<script>
        const isActive = ref(false);
        const toggleAccordion = async (i) => {
            isActive[i] = !isActive[i];        
        };
</script>
        
<template>
    <div>
        <div v-for="item in items" :key="item.id">
            <div class="my-class" :class="{ 'is-open': isActive[i] }" v-html="item.details"></div>
        </div>
        <button @click="() => toggleAccordion(i)">Toggle text</button>
    </div>
</template>

Tha above code actually changes the value of the isActive[i], but it does not toggle my .is-open class on the div in the code.

Can anyone point me in the right direction in this, as I have stared blind on the code or must either have missed something or totally not understood what is wrong?

Thank you very much in advance to anyone that can help out.


Solution

  • First thing, the isActive is a boolean type that does not have any property inside it, so it is throwing the error, "Cannot read properties of undefined (reading 'value')". You need to make isActive an object so it can hold the indexes as properties. Also, you need to use the reactive API, so the changes on an index like isActive[i] will be reactive in the Vue template.

    The script should look something like this-

    <script setup>
    import { reactive } from "vue"
    
    const items = [{"details": "First"}, {"details": "Second"}];
    
    const isActive = reactive({});
    
    const toggleAccordion = async (i) => {
      isActive[i] = isActive[i] ? !isActive[i] : true;
    }; 
    </script>
    

    Here is a working demo, try out.