Skip to content

🖥️ Deleting Games

In this guide, we’ll add functionality that allows users to delete games from the database. This is an essential feature for any CRUD (Create, Read, Update, Delete) application.

To implement delete functionality, we need to:

  1. Update the GameCard component to include a delete button
  2. Create a route that handles the delete action
  3. Connect the button to the route using a form
  4. Implement the server-side logic to delete the game from the database

Let’s break this down into manageable steps.

First, we need to modify our GameCard component to accept a game ID and include a delete button that submits to our delete route.

app/components/GameCard.tsx
import { Form } from "@remix-run/react";
interface GameCardProps {
id: string;
title: string;
releaseDate: string;
genre: string;
imageUrl: string;
}
export default function GameCard({
id,
title,
releaseDate,
genre,
imageUrl,
}: GameCardProps) {
const formattedDate = releaseDate.substring(0, 10);
return (
<div className="flex flex-col gap-4">
<div className="relative h-72 overflow-hidden">
<img
src={imageUrl}
alt={`${title} cover`}
className="absolute inset-0 w-full h-full object-cover rounded-xl"
/>
</div>
<div className="flex justify-between">
<div className="flex flex-col justify-between w-2/3">
<h3 className="font-bold text-2xl text-slate-300">{title}</h3>
<p className="text-cyan-300 uppercase text-sm font-semibold">
{genre}
</p>
<p className="text-slate-200/60 text-sm font-semibold">
{formattedDate}
</p>
</div>
<div className="flex flex-col gap-7 w-1/3">
<button className="border-2 border-cyan-300 text-cyan-300 p-2 rounded-md transition hover:bg-cyan-50/10">
View
</button>
<button className="border-2 border-red-300 text-red-300 p-2 rounded-md transition hover:bg-red-50/10">
Delete
</button>
<Form action={`/delete-game/${id}`} method="post">
<button
type="submit"
className="border-2 border-red-300 text-red-300 p-2 rounded-md transition hover:bg-red-50/10 w-full"
>
Delete
</button>
</Form>
</div>
</div>
</div>
);
}

Step 2: Pass the Game ID to the GameCard Component

Section titled “Step 2: Pass the Game ID to the GameCard Component”

Now that our GameCard component accepts an ID prop, we need to pass the game ID from the parent component.

Open your app/routes/_index.tsx file and update the GameCard component as follows:

app/routes/_index.tsx
export default function Index() {
const { games } = useLoaderData<typeof loader>();
return (
<div className="container mx-auto px-8 grid grid-cols-3 gap-8">
{games.map((game) => (
<div key={game.id}>
<GameCard
key={game.id}
id={game.id}
title={game.title}
releaseDate={game.releaseDate}
imageUrl={game.imageUrl || fallbackImage}
genre={game.category?.title || "Unknown"}
/>
</div>
))}
</div>
);
}

Step 3: Create a Route to Handle Game Deletion

Section titled “Step 3: Create a Route to Handle Game Deletion”

Now we need to create a route that will handle the delete action. In Remix, we can create a route with a dynamic parameter using the $ symbol in the filename.

Create a new file inside app/routes/ called delete-game.$gameId.tsx and add the following code:

app/routes/delete-game.$gameId.tsx
import { redirect } from "@remix-run/node";
import type { ActionFunctionArgs } from "@remix-run/node";
import { PrismaClient } from "@prisma/client";
export async function action({ params }: ActionFunctionArgs) {
const gameId = params.gameId;
const prisma = new PrismaClient();
// Delete the game from the database
await prisma.game.delete({
where: { id: gameId },
});
prisma.$disconnect();
// Redirect back to the home page
return redirect("/");
}
// No need for a default export since this route only handles the action

A resource route in Remix is a route that doesn’t render a UI component but instead handles data operations. These routes typically:

  • Have an action function, a loader function, or both
  • Don’t have a default export (component)
  • Are used for API-like functionality within your Remix application

Resource routes are perfect for operations like deleting data, as they provide a clean separation between data mutations and UI rendering.

Remix uses file naming conventions to define routes with dynamic parameters:

  • A file named delete-game.$gameId.tsx creates a route that matches /delete-game/123, /delete-game/abc, etc.
  • The $gameId part becomes a parameter that can be accessed via params.gameId in the loader or action function
  • This allows you to create flexible routes that can handle different resources based on the URL

Remix enhances HTML forms with progressive enhancement:

  • When JavaScript is available, Remix intercepts the form submission and handles it with client-side navigation
  • When JavaScript is not available, the form falls back to the browser’s native form submission behavior
  • In both cases, the action function in the target route is called to handle the submission

This approach provides a robust user experience that works even if JavaScript fails to load or execute.

In this tutorial, we’ve:

  1. Updated the GameCard component to include a delete button
  2. Created a resource route to handle the delete action
  3. Connected the button to the route using Remix’s Form component
  4. Implemented the server-side logic to delete the game from the database

These changes allow users to delete games from the database with a simple button click, completing the “D” in CRUD functionality for our application.

Great job! You’ve successfully implemented delete functionality in your application. This is an essential feature for any data-driven application, allowing users to manage their content effectively. With this addition, your GameLog application now supports both creating and deleting games, making it more useful and complete.

Click to reveal