# [Next.js + TS 실전] 7. 삭제 확인 UI

[Next.js + TS 실전] 7. 삭제 확인 UI

지금은 상세 페이지에서 삭제를 누르면 바로 지워집니다.
이번 편은 실수로 누르는 걸 막는 확인 단계를 붙입니다.
목표: window.confirm → 작은 Client 버튼 컴포넌트.


이 편에서 할 일

  1. 왜 확인이 필요한지 이해하기
  2. DeleteMemoButton 클라이언트 컴포넌트 만들기
  3. 상세 페이지 폼에 연결하기
  4. (선택) 간단한 모달로 바꾸기

Server Action·DB는 그대로입니다.
누르는 순간만 Client에서 한 번 막습니다.


지금 코드의 문제

상세 페이지 대략:

<form action={deleteMemoAction}>
  <input type="hidden" name="id" value={memo.id} />
  <button type="submit">삭제</button>
</form>

한 번 클릭 = 즉시 삭제 + /memos로 이동.

실무에서는 보통:

  1. 삭제 클릭
  2. “정말 삭제할까요?”
  3. 확인 → 그때 서버로 전송

접근 두 가지

방식 장점 단점
window.confirm 코드 최소, 바로 동작 브라우저 기본 UI라 못생김
모달 디자인·문구 자유 컴포넌트가 조금 김

이번 편 추천은 confirm 먼저입니다.
패턴만 익히고, 나중에 모달로 갈아끼우면 됩니다.


1) DeleteMemoButton 만들기

경로: components/DeleteMemoButton.tsx

"use client";

type DeleteMemoButtonProps = {
  memoTitle: string;
};

export default function DeleteMemoButton({ memoTitle }: DeleteMemoButtonProps) {
  function handleClick(e: React.MouseEvent<HTMLButtonElement>) {
    const ok = window.confirm(
      `"${memoTitle}" 메모를 삭제할까요?\n삭제하면 되돌릴 수 없습니다.`
    );

    if (!ok) {
      e.preventDefault();
    }
  }

  return (
    <button type="submit" onClick={handleClick}>
      삭제
    </button>
  );
}

포인트:

  • 'use client'onClick / window 사용
  • type="submit" → 확인했을 때만 폼이 제출됨
  • preventDefault() → 취소하면 서버 Action이 안 불림
  • 페이지 전체가 아니라 버튼만 Client (5편 패턴)

2) 상세 페이지에 연결

app/memos/[id]/page.tsx의 삭제 부분만 바꿉니다.

import Link from "next/link";
import { notFound } from "next/navigation";
import { deleteMemoAction } from "@/app/memos/actions";
import { getMemoById } from "@/lib/memos";
import DeleteMemoButton from "@/components/DeleteMemoButton";

type MemoDetailPageProps = {
  params: Promise<{ id: string }>;
};

export default async function MemoDetailPage({ params }: MemoDetailPageProps) {
  const { id } = await params;
  const memo = await getMemoById(id);

  if (!memo) {
    notFound();
  }

  return (
    <main>
      <h1>{memo.title}</h1>
      <p>{memo.content}</p>
      <time dateTime={memo.createdAt}>{memo.createdAt}</time>

      <p>
        <Link href={`/memos/${memo.id}/edit`}>수정</Link>
      </p>

      <form action={deleteMemoAction}>
        <input type="hidden" name="id" value={memo.id} />
        <DeleteMemoButton memoTitle={memo.title} />
      </form>
    </main>
  );
}

Clerk를 붙였다면 getMemoById(id, userId) 형태일 수 있습니다.
이번 편에서 바꾸는 건 삭제 버튼뿐입니다.


동작 확인

  1. /memos/어떤id 상세로 이동
  2. 삭제 클릭
  3. 확인 창에서 취소 → 그대로 상세에 머무름
  4. 다시 삭제확인 → 목록으로 이동하고 메모 사라짐

(선택) 간단한 모달

confirm이 밋밋하면 같은 자리에 모달을 넣을 수 있습니다.

"use client";

import { useState } from "react";

type DeleteMemoButtonProps = {
  memoTitle: string;
};

export default function DeleteMemoButton({ memoTitle }: DeleteMemoButtonProps) {
  const [open, setOpen] = useState(false);

  return (
    <>
      <button type="button" onClick={() => setOpen(true)}>
        삭제
      </button>

      {open && (
        <div
          role="dialog"
          aria-modal="true"
          style={{
            position: "fixed",
            inset: 0,
            background: "rgba(0,0,0,0.4)",
            display: "grid",
            placeItems: "center",
          }}
        >
          <div style={{ background: "white", padding: 16, minWidth: 280 }}>
            <p>
              <strong>{memoTitle}</strong> 을(를) 삭제할까요?
            </p>
            <p>삭제하면 되돌릴 수 없습니다.</p>

            <button type="button" onClick={() => setOpen(false)}>
              취소
            </button>
            {" "}
            <button type="submit" onClick={() => setOpen(false)}>
              삭제하기
            </button>
          </div>
        </div>
      )}
    </>
  );
}

주의:

  • 바깥 삭제type="button" (폼 즉시 제출 방지)
  • 모달 안 삭제하기type="submit"
  • 이 버튼들은 반드시 <form action={deleteMemoAction}> 안에 있어야 함

접근성·포커스 트랩까지 가면 라이브러리(모달)를 쓰는 편이 낫습니다.
학습용으로는 위 수준이면 충분합니다.


왜 Action은 그대로인가

확인 UI는 브라우저 UX입니다.
진짜 삭제는 여전히 서버의 deleteMemoAction이 합니다.

그래서:

  • 확인을 우회하는 요청이 올 수도 있음 → Action에서 id/권한 검증은 유지
  • UI만 Client, 삭제로직은 Server

보안을 confirm에 맡기지 마세요.
confirm은 실수 방지용입니다.


자주 하는 실수

1. 페이지 전체를 'use client'
→ 버튼만 Client로 분리

2. confirm 취소를 안 막고 submit
preventDefault() 필요

3. 모달의 삭제 버튼을 form 밖에 둠
action이 연결되지 않음

4. type="submit"인 첫 버튼이 모달을 안 거침
→ 열기 버튼은 type="button"


이 편에서 가져갈 것

  • 삭제는 확인 한 단계가 있는 편이 안전하다
  • window.confirm으로 최소 구현 가능하다
  • 인터랙션만 Client 컴포넌트로 뺀다
  • 확인 UI ≠ 권한/검증 (Server Action은 그대로)

다음으로 이어가면 좋은 것

  • 목록 검색·정렬
  • 입문+실전 FAQ / 시리즈 총정리

시리즈 목차 (실전)

  1. 메모 수정과 삭제
  2. Prisma + SQLite로 DB 붙이기
  3. Vercel에 배포하기
  4. 폼 검증하기 (zod)
  5. 로그인 붙이기 (Clerk)
  6. Neon Postgres 연결하기
  7. 삭제 확인 UI ← 지금