Search code examples
vue.jsclicksizetailwind-cssv-for

Only select post Im clicking on (v-for @click vue.js) but its selecting all saved posts because of the v-for, what can I use instead?


Im doing a little blog project to practice vue.js. Im using composition API script setup. When you write a post it saves in localStorage. When you click on the text in the blog it opens up an overlay and a big sized blogtext. its just that its not only showing the one I clicked on but the other earlier blog posts aswell. How can I focus so it only shows the one I click on. Im using tailwind as css.

enter image description here

enter image description here

On this link you can test yourself but here I have some dummytext in before I made it so the note.text will be shown; https://unique-sprinkles-d1a6cd.netlify.app/

<template>
    <div v-if="showModal" @click="showModal = false" class="absolute w-screen h-screen bg-black opacity-75 z-10 flex cursor-pointer items-center justify-center">
        <div v-for="note in notes" :key="note.id" class="bg-white  hover:border-red-400 p-4 rounded-full text-2xl font-bold"><p>{{ note.text }}</p></div>
    </div>
<div class="w-6/12 text-center m-auto">
    <h1 class="text-4xl font-bold text-indigo-500 m-5">Blog Posts</h1>
    <div  class="border-2 border-slate-100 rounded-3xl hover:border-red-400" v-for="note in notes" :key="note.id" > 
        <div class="relative flex flex-col m-0 justify-around cursor-pointer">
            <button class="absolute top-0 right-0 w-5 h-5 cursor-pointer rounded-full hover:bg-red-400 p-2.5" @click="deleteBtn(note)">x</button>
            <img class="w-24 h-24 p-3.5 rounded-full" :src="note.img"> 
            <p class="text-xs text-right pr-3.5"> {{ note.writer }}</p>
            <p class="text-lg font-bold"> {{ note.headline }}</p>
            <p @click="showModal = true" class="text-left p-3.5 text-sm"> {{ note.text }}</p>
        </div>
    </div>
</div>
</template>

<script setup>
import { ref } from 'vue';

const notes = ref([]);

notes.value = JSON.parse(localStorage.getItem('notes'));

    function deleteBtn(note){
        notes.value.splice(notes.value.indexOf(note), 1);
        localStorage.setItem('notes', JSON.stringify(notes.value));


        notes.value = JSON.parse(localStorage.getItem('notes'));
        console.log(notes.value)
    }

const showModal = ref(false)
</script>

after changes it looks like this; How can I make the empty squares dissapear and also the opacity to 0 in the white textarea so no text from the behind shows. Ive tried tailwind opacity functions but doesnt work. enter image description here


Solution

  • Here's a method you can use for conditional rendering.

    When dealing with v-loops without using a child component, you can use index in the loop then conditionally render it with v-if="selectedIndex = index.

    In the modal section, you add the v-if like this

    <div 
      v-if="showModal" 
      @click="showModal = false" 
      class="absolute w-screen h-screen bg-black opacity-75 z-10 flex cursor-pointer items-center justify-center"
    >
        <div 
          v-for="(note, index) in notes" 
          :key="note.id" 
          class="bg-white hover:border-red-400 p-4 rounded-full text-2xl font-bold"
        >
          <p v-if="selectedIndex === index">{{ note.text }}</p>
        </div>
    </div>
    

    then in the place where you rendered the text to be clicked, you add a click event to update selectedIndex when you click on different rendered contents with their relevant index.

    <div
      v-for="(note, index) in notes"
      :key="note.id"
      class="border-2 border-slate-100 rounded-3xl hover:border-red-400"
      @click="selectedIndex = index"
    > 
       <div 
          class="relative flex flex-col m-0 justify-around cursor-pointer"
       >
         ...
       </div>
    </div>
    

    then in the script tag, just add a ref of the selectedIndex

    const selectedIndex = ref(null)
    

    Note: If your @click="showModal = true" is faster than the selectedIndex update, you will get a "delayed" update. To fix this you want to convert the @click="showModal = true" into a function, which includes the updateIndex and this showModal reassigning.

    It would look something like this

    <template>
    // other parts...
    <p @click="updateValues(index)" class="text-left p-3.5 text-sm"> {{ note.text }}</p>
    // other parts...
    </template>
    
    <script setup>
    // ... your other functions above
    
    const updateValues = (index) => {
      selectedIndex.value = index
      showModal.value = true
    }
    </script>
    

    Anyhow, I think it would be better to split those 2 items (the modal and the render text) into different components, that way you will be able to have an easier time handling the logic.