🖥️ 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.
Understanding the Challenge
Section titled “Understanding the Challenge”To implement delete functionality, we need to:
- Update the
GameCardcomponent to include a delete button - Create a route that handles the delete action
- Connect the button to the route using a form
- Implement the server-side logic to delete the game from the database
Let’s break this down into manageable steps.
Step 1: Update the GameCard Component
Section titled “Step 1: Update the GameCard Component”First, we need to modify our GameCard component to accept a game ID and include a delete button that submits to our delete route.
Step 2: Implement the Delete Button
Section titled “Step 2: Implement the Delete Button”In the same file, update the GameCard component to include a delete button that submits to our delete route:
Your code will be different to that shown below, as you have implemented your own JSX for the GameCard component.
THe important thing is that you look where your dummy button is, and replace it with the code below.
import { Form } from "@remix-run/react";
interface GameCardProps { id: number; 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 pr-4"> <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-4 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> );}We’ve made several important changes to the GameCard component:
- Imported the Form component: We’re using Remix’s
Formcomponent to handle the delete action. - Added an id prop: We need the game’s ID to know which game to delete.
- Replaced the button with a Form: Instead of a simple button, we now have a form that submits to a specific route.
- Set the action and method: The form submits to
/delete-game/${id}with a POST method (this route doesn’t exist yet!)
The Form component is a key part of Remix’s data mutation strategy. It enhances the HTML <form> element with JavaScript-powered submissions while maintaining the browser’s native form behaviour as a fallback.
With this done, your typical GameCard component should look similar to the one below:

If you try clicking the delete button, you will get a 404 (page not found) error.
Don’t worry - we will fix this shortly.
Step 3: Pass the Game ID to the GameCard Component
Section titled “Step 3: 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.
Can you update the GameCard component to pass the game ID as a prop?
Open your app/routes/_index.tsx file and update the GameCard props where you think it needs to be changed.
When you are done, compare your solution to the one below:
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> );}This is a simple change - we’re just passing the game.id as a prop to the GameCard component. This ID will be used to construct the form action URL.
Step 4: Create a Route to Handle Game Deletion
Section titled “Step 4: 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:
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["game-id"];
if (!gameId) { throw new Error("Game ID is required"); }
const parsedId = parseInt(gameId!);
const prisma = new PrismaClient();
// Delete the game from the database await prisma.game.delete({ where: { id: parsedId }, });
prisma.$disconnect();
// Redirect back to the home page return redirect("/");}
// No need for a default export since this route only handles the actionThis route file contains only an action function, with no default export for rendering a component. This is because it’s a “resource route” that only handles data mutations, not UI rendering.
Key points about this route:
- Dynamic Parameter: The
$gameIdin the filename becomes a parameter in theparamsobject. - Action Function: This function runs when a form is submitted to this route with a POST request.
- Prisma Delete Operation: We use Prisma’s
deletemethod to remove the game from the database. - Redirect: After deletion, we redirect the user back to the home page.
The route naming convention (delete-game.$gameId.tsx) is part of Remix’s file-based routing system. The $gameId segment becomes a dynamic parameter that can be accessed via params.gameId.
Understanding Key Concepts
Section titled “Understanding Key Concepts”Remix Resource Routes
Section titled “Remix Resource Routes”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.
Dynamic Route Parameters
Section titled “Dynamic Route Parameters”Remix uses file naming conventions to define routes with dynamic parameters:
- A file named
delete-game.$gameId.tsxcreates a route that matches/delete-game/123,/delete-game/abc, etc. - The
$gameIdpart becomes a parameter that can be accessed viaparams.gameIdin the loader or action function - This allows you to create flexible routes that can handle different resources based on the URL
Form Submissions in Remix
Section titled “Form Submissions in Remix”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 behaviour
- 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.
What We’ve Learned
Section titled “What We’ve Learned”In this tutorial, we’ve:
- Updated the
GameCardcomponent to include a delete button - Created a resource route to handle the delete action
- Connected the button to the route using Remix’s
Formcomponent - 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.