CodingNic

Deleting & Favoriting

Add Delete and Favorite Data Functions, Build the Favorite Toggle

Deleting & Favoriting 25 min read

Add Delete and Favorite Data Functions, Build the Favorite Toggle

Add Delete and Favorite Data Functions, Build the Favorite Toggle

Data functions

Open lib/contacts.ts and add:

typescript
export async function deleteContact(id: string): Promise<void> {
  await pool.query(`DELETE FROM contacts WHERE id = $1`, [id]);
}

export async function toggleFavorite(id: string): Promise<void> {
  await pool.query(
    `UPDATE contacts SET favorite = NOT favorite, updated_at = now() WHERE id = $1`,
    [id]
  );
}

NOT favorite flips the boolean in place, right in SQL — no need to read
the current value first and write back its opposite.

Server Actions

Open lib/actions.ts and add:

typescript
import { deleteContact, toggleFavorite } from "./contacts";

export async function deleteContactAction(id: string) {
  await deleteContact(id);
  revalidatePath("/");
  redirect("/");
}

export async function toggleFavoriteAction(formData: FormData) {
  const id = String(formData.get("id"));
  const returnTo = String(formData.get("returnTo") || "/");
  await toggleFavorite(id);
  revalidatePath("/");
  revalidatePath(`/contacts/${id}`);
  redirect(returnTo);
}

toggleFavoriteAction takes a returnTo field so the same action works
whether it’s clicked from the Home page or the contact’s own detail page —
either way, the redirect sends the learner right back where they were.

Build the favorite toggle

Create components/FavoriteToggleForm.tsx:

tsx
import { toggleFavoriteAction } from "@/lib/actions";

export default function FavoriteToggleForm({
  id,
  favorite,
  returnTo,
}: {
  id: string;
  favorite: boolean;
  returnTo: string;
}) {
  return (
    <form action={toggleFavoriteAction} className="inline">
      <input type="hidden" name="id" value={id} />
      <input type="hidden" name="returnTo" value={returnTo} />
      <button type="submit" className={`fav-btn ${favorite ? "is-fav" : ""}`} title={favorite ? "Unfavorite" : "Mark as favorite"}>
        {favorite ? "★" : "☆"}
      </button>
    </form>
  );
}

This is a plain Server Component — no "use client" directive anywhere.
The id and returnTo values ride along as hidden form fields, so the
Server Action receives everything it needs from formData alone. Clicking
the star submits the form, runs the action on the server, and the page
redirects back with the new state — all without a single line of
client-side JavaScript.

Add it to the contact detail page

Open app/contacts/[id]/page.tsx and update the header:

tsx
import FavoriteToggleForm from "@/components/FavoriteToggleForm";

// ...inside the component, in detail-header:
<h1>
  {contact.name}
  <FavoriteToggleForm id={contact.id} favorite={contact.favorite} returnTo={`/contacts/${contact.id}`} />
</h1>

Test it

Open any contact’s detail page and click the star. The page should reload
with the star now filled in (or emptied out), and the change should also
show up back on the Home page — confirming revalidatePath("/") kept both
views in sync.

Checkpoint

You can favorite and unfavorite a contact from its detail page with a
single click, using only a Server Component and a Server Action — no
client-side state involved.