V-Dialog Delete Confirmation
March 22, 2025 · AI Conversations
—
### ✅ Example (Vue 3 + Vuetify 3)
#### 1. **Template:**
<template>
<div>
<v-btn color="error" @click="openDialog(item)">Delete</v-btn>
<v-dialog v-model="dialog" max-width="400">
<v-card>
<v-card-title class="text-h6">Confirm Deletion</v-card-title>
<v-card-text>Are you sure you want to delete <strong>{{ selectedItem?.name }}</strong>?</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn text @click="dialog = false">Cancel</v-btn>
<v-btn color="error" @click="confirmDelete">Delete</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</div>
</template>
—
#### 2. **Script:**
<script setup>
import { ref } from 'vue'
const dialog = ref(false)
const selectedItem = ref(null)
const openDialog = (item) => {
selectedItem.value = item
dialog.value = true
}
const confirmDelete = () => {
// Call your delete function here, e.g., Axios request or emit event
console.log('Deleting item:', selectedItem.value)
dialog.value = false
}
</script>
—
You can slot this into a loop if you're rendering multiple items and want to delete each individually.
Let me know if you're using Laravel with Inertia or need this wired into an API call (Axios, fetch, etc.) — I can help set that up too.
<script>
import { ref,onMounted } from 'vue';
import {useRouter} from 'vue-router';
import { useLoadingStore } from '@/stores/loading';
const loadingStore = useLoadingStore();
const router = useRouter();
import {
fetchPortfolioCategories,
deletePortfolioCategory
} from '@/dbstore/firebase/myPortfolio'
interface PortfolioCategory {
id: string;
name: string;
createdAt: any;
description: string;
image: string;
imagePath: string;
updatedAt: any;
}
const portfolioItems = ref<PortfolioCategory[]>([])
const loadItems = async () => {
portfolioItems.value = await fetchPortfolioCategories() as PortfolioCategory[];
console.log(portfolioItems.value);
}
const deleteItem = async (id: string) => {
try {
loadingStore.setLoading(true);
await deletePortfolioCategory(id)
loadItems()
} catch (error) {
console.error('Error deleting portfolio item:', error)
} finally {
loadingStore.setLoading(false);
}
}
onMounted(loadItems)
</script>
Add Category
Doc ID
Title
Actions
{{ item.id }}
{{ item.name }}
1. Add a `v-dialog` component.
2. Store the selected item (or item ID) to be deleted.
3. Open the dialog when delete is clicked.
4. Confirm deletion inside the dialog.
Here’s your updated code with the `v-dialog` integrated:
—
### ✅ **Updated Script Section**
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { useRouter } from 'vue-router';
import { useLoadingStore } from '@/stores/loading';
import {
fetchPortfolioCategories,
deletePortfolioCategory
} from '@/dbstore/firebase/myPortfolio';
const loadingStore = useLoadingStore();
const router = useRouter();
interface PortfolioCategory {
id: string;
name: string;
createdAt: any;
description: string;
image: string;
imagePath: string;
updatedAt: any;
}
const portfolioItems = ref<PortfolioCategory[]>([])
const dialog = ref(false)
const selectedItemId = ref<string | null>(null)
const loadItems = async () => {
portfolioItems.value = await fetchPortfolioCategories() as PortfolioCategory[];
console.log(portfolioItems.value);
}
const openDeleteDialog = (id: string) => {
selectedItemId.value = id
dialog.value = true
}
const confirmDelete = async () => {
if (!selectedItemId.value) return
try {
loadingStore.setLoading(true);
await deletePortfolioCategory(selectedItemId.value)
await loadItems()
} catch (error) {
console.error('Error deleting portfolio item:', error)
} finally {
dialog.value = false
selectedItemId.value = null
loadingStore.setLoading(false);
}
}
onMounted(loadItems)
</script>
—
### ✅ **Updated Template Section**
Add the `v-dialog` component outside of your `v-table`:
<template>
<v-container>
<v-row>
<v-col align="end">
<v-btn
size="x-large"
class="mt-4"
color="primary"
@click="router.push('/user/portfolio/categories/add')"
>
Add Category
</v-btn>
</v-col>
</v-row>
<v-row>
<v-col cols="12" md="12">
<v-table density="compact">
<thead>
<tr>
<th class="text-left">Doc ID</th>
<th class="text-left">Title</th>
<th class="text-left">Actions</th>
</tr>
</thead>
<tbody>
<tr v-for="item in portfolioItems" :key="item.id">
<td>{{ item.id }}</td>
<td class="text-left">{{ item.name }}</td>
<td class="pt-2 pb-2">
<v-btn
icon="mdi-delete"
size="small"
color="error"
@click="openDeleteDialog(item.id)"
></v-btn>
<v-btn
icon="mdi-pencil"
class="ml-2"
size="small"
color="primary"
@click="router.push('/user/portfolio/categories/edit/' + item.id)"
></v-btn>
</td>
</tr>
</tbody>
</v-table>
</v-col>
</v-row>
<!-- Delete Confirmation Dialog -->
<v-dialog v-model="dialog" max-width="400">
<v-card>
<v-card-title class="text-h6">Confirm Deletion</v-card-title>
<v-card-text>Are you sure you want to delete this item?</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn text @click="dialog = false">Cancel</v-btn>
<v-btn color="error" @click="confirmDelete">Delete</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</v-container>
</template>
—
Let me know if you'd like to show the item name in the dialog (e.g., “Are you sure you want to delete **{{ item.name }}**?”), or want to show a toast/snackbar on success.
—
### ✅ Step 1: Create `ConfirmDialog.vue`
Create a new component:
`src/components/ConfirmDialog.vue`
<template>
<v-dialog v-model="modelValue" max-width="400">
<v-card>
<v-card-title class="text-h6">{{ title }}</v-card-title>
<v-card-text>{{ message }}</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn text @click="$emit('cancel')">Cancel</v-btn>
<v-btn color="error" @click="$emit('confirm')">Delete</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</template>
<script setup lang="ts">
defineProps<{
modelValue: boolean
title: string
message: string
}>()
defineEmits(['update:modelValue', 'confirm', 'cancel'])
</script>
—
### ✅ Step 2: Use It in Your Page
Update your page to import and use the `ConfirmDialog.vue` component.
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { useRouter } from 'vue-router';
import { useLoadingStore } from '@/stores/loading';
import ConfirmDialog from '@/components/ConfirmDialog.vue';
import {
fetchPortfolioCategories,
deletePortfolioCategory
} from '@/dbstore/firebase/myPortfolio';
interface PortfolioCategory {
id: string;
name: string;
createdAt: any;
description: string;
image: string;
imagePath: string;
updatedAt: any;
}
const loadingStore = useLoadingStore();
const router = useRouter();
const portfolioItems = ref<PortfolioCategory[]>([])
const dialog = ref(false)
const selectedItem = ref<PortfolioCategory | null>(null)
const loadItems = async () => {
portfolioItems.value = await fetchPortfolioCategories() as PortfolioCategory[];
}
const openDeleteDialog = (item: PortfolioCategory) => {
selectedItem.value = item
dialog.value = true
}
const confirmDelete = async () => {
if (!selectedItem.value) return
try {
loadingStore.setLoading(true);
await deletePortfolioCategory(selectedItem.value.id)
await loadItems()
} catch (error) {
console.error('Error deleting portfolio item:', error)
} finally {
dialog.value = false
selectedItem.value = null
loadingStore.setLoading(false);
}
}
onMounted(loadItems)
</script>
—
### ✅ Template Update
<template>
<v-container>
<v-row>
<v-col align="end">
<v-btn
size="x-large"
class="mt-4"
color="primary"
@click="router.push('/user/portfolio/categories/add')"
>
Add Category
</v-btn>
</v-col>
</v-row>
<v-row>
<v-col cols="12" md="12">
<v-table density="compact">
<thead>
<tr>
<th class="text-left">Doc ID</th>
<th class="text-left">Title</th>
<th class="text-left">Actions</th>
</tr>
</thead>
<tbody>
<tr v-for="item in portfolioItems" :key="item.id">
<td>{{ item.id }}</td>
<td class="text-left">{{ item.name }}</td>
<td class="pt-2 pb-2">
<v-btn icon="mdi-delete" size="small" color="error" @click="openDeleteDialog(item)" />
<v-btn icon="mdi-pencil" class="ml-2" size="small" color="primary" @click="router.push('/user/portfolio/categories/edit/' + item.id)" />
</td>
</tr>
</tbody>
</v-table>
</v-col>
</v-row>
<!-- Reusable Delete Dialog Component -->
<ConfirmDialog
v-model="dialog"
:title="'Confirm Deletion'"
:message="`Are you sure you want to delete '${selectedItem?.name}'?`"
@confirm="confirmDelete"
@cancel="dialog = false"
/>
</v-container>
</template>
—
Now your delete dialog is fully reusable and cleanly separated!
You can reuse `ConfirmDialog` anywhere across your app for other confirmation actions as well.
Want to make it more flexible (custom button labels, icons, colors)? I can help you expand it too.
Use a v-bind binding combined with a v-on listener that emits update:x event instead.
To fix this cleanly, we’ll use the `modelValue` pattern with an `emit('update:modelValue', value)` — Vuetify 3 and Vue 3 encourage this approach.
—
### ✅ Step-by-step fix:
#### ✅ **Step 1: Update `ConfirmDialog.vue`**
<template>
<v-dialog
:model-value="modelValue"
@update:model-value="emit('update:modelValue', $event)"
max-width="400"
>
<v-card>
<v-card-title class="text-h6">{{ title }}</v-card-title>
<v-card-text>{{ message }}</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn text @click="emit('cancel')">Cancel</v-btn>
<v-btn color="error" @click="emit('confirm')">Delete</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</template>
<script setup lang="ts">
defineProps<{
modelValue: boolean
title: string
message: string
}>()
const emit = defineEmits(['update:modelValue', 'confirm', 'cancel'])
</script>
✅ This way, the component can still use `v-model` from the **parent**, and emit `update:modelValue` internally when needed.
—
#### ✅ Step 2: Use `v-model` safely in the parent
No change needed here — your usage is already correct:
<ConfirmDialog
v-model="dialog"
:title="'Confirm Deletion'"
:message="`Are you sure you want to delete '${selectedItem?.name}'?`"
@confirm="confirmDelete"
@cancel="dialog = false"
/>
—
That warning will now disappear ✅
Want to make this dialog reusable with different button labels or color themes? I can help add that next!
—
### ✅ Step 1: **Update the `ConfirmDialog.vue` component**
Add a prop for `item` and use it inside if needed.
<template>
<v-dialog
:model-value="modelValue"
@update:model-value="emit('update:modelValue', $event)"
max-width="400"
>
<v-card>
<v-card-title class="text-h6">{{ title }}</v-card-title>
<v-card-text>
{{ message }}
<div v-if="item">
<p><strong>ID:</strong> {{ item.id }}</p>
<p><strong>Name:</strong> {{ item.name }}</p>
</div>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn text @click="emit('cancel')">Cancel</v-btn>
<v-btn color="error" @click="emit('confirm', item)">Delete</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</template>
<script setup lang="ts">
const props = defineProps<{
modelValue: boolean
title: string
message: string
item?: Record<string, any>
}>()
const emit = defineEmits(['update:modelValue', 'confirm', 'cancel'])
</script>
✅ We're now accepting an `item` prop and also **emitting it back on confirm**, so the parent can receive the full object.
—
### ✅ Step 2: **Pass `selectedItem` and handle the `confirm` with item**
In your parent template:
<ConfirmDialog
v-model="dialog"
:title="'Confirm Deletion'"
:message="`Are you sure you want to delete '${selectedItem?.name}'?`"
:item="selectedItem"
@confirm="confirmDelete"
@cancel="dialog = false"
/>
Then update the `confirmDelete` function to accept the item:
const confirmDelete = async (item: PortfolioCategory) => {
try {
loadingStore.setLoading(true);
await deletePortfolioCategory(item.id);
await loadItems();
} catch (error) {
console.error('Error deleting portfolio item:', error);
} finally {
dialog.value = false;
selectedItem.value = null;
loadingStore.setLoading(false);
}
};
—
### ✅ Final touch
Since the dialog emits `@confirm="confirmDelete"` with the item as payload, update the binding in your `openDeleteDialog`:
const openDeleteDialog = (item: PortfolioCategory) => {
selectedItem.value = item;
dialog.value = true;
};
—
Let me know if you want the dialog to support custom actions or slot content too!
### ✅ Basic `v-if`, `v-else-if`, and `v-else`
<template>
<div>
<p v-if="status === 'loading'">Loading...</p>
<p v-else-if="status === 'error'">Something went wrong.</p>
<p v-else>Data loaded successfully.</p>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
const status = ref('loading') // or 'error' or 'success'
</script>
—
### ✅ Inside a loop (`v-for`)
<template>
<div v-for="item in items" :key="item.id">
<p v-if="item.active">Active: {{ item.name }}</p>
<p v-else>Inactive: {{ item.name }}</p>
</div>
</template>
—
### ✅ With a Boolean Toggle
<template>
<div>
<v-btn @click="show = !show">Toggle</v-btn>
<p v-if="show">Now you see me!</p>
<p v-else>Now you don’t!</p>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
const show = ref(true)
</script>
—
Let me know your exact use case (e.g., inside a component, table row, dialog body), and I’ll tailor it to your needs.