Courses

React Laravel 12 Starter Kit: CRUD Project

Delete Task and Shadcn Toast Notification

Summary of this lesson:
- Adding delete functionality to the tasks table
- Using Shadcn Button component with destructive variant
- Implementing confirmation before delete
- Adding success notifications with Sonner toast component

In this lesson, let's add the Delete button to our table.


Delete Button

Here's what we have in the Controller:

app/Http/Controllers/TaskController.php:

public function destroy(Task $task)
{
$task->delete();
 
return redirect()->route('tasks.index');
}

Since we have Route::resource() here, the React component should fire a DELETE request to the route /tasks/{ID}.

So, first, we define the function inside the React component.

resources/js/pages/Tasks/Index.tsx:

import { Head } from '@inertiajs/react';
import { Head, router } from '@inertiajs/react';
 
// ...
 
export default function Index({ tasks }: { tasks: Task[] }) {
const deleteTask = (id: number) => {
if (confirm('Are you sure?')) {
router.delete(route('tasks.destroy', { id }));
}
};
 
// ...

Then, we need to add a Button to the table that calls the deleteTask() method.

The Shadcn Button component is already installed in the starter kit, so we don't need to run any npx commands. We just need to import it with button variants.

We add a new <TableCell> to our <TableRow>.

resources/js/pages/Tasks/Index.tsx:

import { Button, buttonVariants } from '@/components/ui/button';
 
// ...
 
{tasks.map((task) => (
<TableRow key={task.id}>
<TableCell>{task.name}</TableCell>
<TableCell className={task.is_completed ? 'text-green-600' : 'text-red-700'}>
{task.is_completed ? 'Completed' : 'In Progress'}
</TableCell>
<TableCell className="flex flex-row gap-x-2 text-right">
<Button variant={'destructive'} className={'cursor-pointer'} onClick={() => deleteTask(task.id)}>
Delete
</Button>
</TableCell>
</TableRow>
))}

Here's the visual result:

Now, see that variant={'destructive'}? To see what other button variants are available, look at this Shadcn documentation page.

Notice: In this course, we're adding custom Tailwind CSS classes to various components. I don't explicitly emphasize this, but it's pretty important: you must be good with Tailwind to customize the design to your needs.

If we click that Delete button, it will actually work: show a JS confirmation dialog and delete the task if confirmed.

For a better UX, we need to add the notification that the Delete action was successful.


Notifications with Toast/Sonner

To add a success alert/notification, we will use another Shadcn component called Sonner. They describe it as "opinionated toast component for React".

To install it, we run this command:

npx shadcn@latest add sonner

And this is where we get to a tricky situation with Shadcn and the latest (currently) React 19 version. See the warning in the Terminal:

It's not an error. The thing is: not all Shadcn components fully support the latest React 19. You can read more about it on this official Shadcn documentation page.

In our case, at the time of writing, we need to choose to Use --force and proceed.

As a result, we have a new file resources/js/components/ui/sonner.tsx created.

Now, we can use it in our React component.

First, we show that notification from our Index.tsx file.

resources/js/pages/Tasks/Index.tsx:

import { toast } from 'sonner';
 
// ...
 
export default function Index({ tasks }: { tasks: Task[] }) {
const deleteTask = (id: number) => {
if (confirm('Are you sure?')) {
router.delete(route('tasks.destroy', { id }));
toast.success('Task deleted successfully');
}
};

And then, we need to define where we show that notification.

Let's do it in the Header, in the top-right corner. In the app-header-layout.tsx, we need to import the <Toaster> component on top and use it in the AppHeaderLayout() function.

resources/js/layouts/app/app-header-layout.tsx:

import { AppShell } from '@/components/app-shell';
import { type BreadcrumbItem } from '@/types';
import type { PropsWithChildren } from 'react';
import { Toaster } from '@/components/ui/sonner';
 
export default function AppHeaderLayout({ children, breadcrumbs }: PropsWithChildren<{ breadcrumbs?: BreadcrumbItem[] }>) {
return (
<AppShell>
...
<Toaster position={'top-right'} />
</AppShell>
);
}

As a result, if we delete a task, we see this in the top-right corner:

Success!

Here are the GitHub commits: first and second for this lesson.


In the next lesson, we will build the Create form of the CRUD.

Previous: Shadcn Table: Tasks List with TypeScript
avatar
const deleteTask = (task: Task) => {
        if (confirm('Are you sure?')) {
            router.delete(route('tasks.destroy', { task }));
            toast.success('Task deleted successfully');
        }
    };
<TableCell className="flex flex-row gap-x-2 text-right">
		<Button variant={'destructive'} className={'cursor-pointer'} onClick={() => deleteTask(task)}>
				Delete
		</Button>
</TableCell>

Dear sir, can we can use task instead id ? In route and controller we also use model binding

avatar
You can use Markdown
avatar
You can use Markdown