[Next.js + TS 실전] 7. 삭제 확인 UI
지금은 상세 페이지에서 삭제를 누르면 바로 지워집니다.
이번 편은 실수로 누르는 걸 막는 확인 단계를 붙입니다.
목표:window.confirm→ 작은 Client 버튼 컴포넌트.
이 편에서 할 일
- 왜 확인이 필요한지 이해하기
DeleteMemoButton클라이언트 컴포넌트 만들기- 상세 페이지 폼에 연결하기
- (선택) 간단한 모달로 바꾸기
Server Action·DB는 그대로입니다.
누르는 순간만 Client에서 한 번 막습니다.
지금 코드의 문제
상세 페이지 대략:
<form action={deleteMemoAction}>
<input type="hidden" name="id" value={memo.id} />
<button type="submit">삭제</button>
</form>
한 번 클릭 = 즉시 삭제 + /memos로 이동.
실무에서는 보통:
- 삭제 클릭
- “정말 삭제할까요?”
- 확인 → 그때 서버로 전송
접근 두 가지
| 방식 | 장점 | 단점 |
|---|---|---|
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)형태일 수 있습니다.
이번 편에서 바꾸는 건 삭제 버튼뿐입니다.
동작 확인
/memos/어떤id상세로 이동- 삭제 클릭
- 확인 창에서 취소 → 그대로 상세에 머무름
- 다시 삭제 → 확인 → 목록으로 이동하고 메모 사라짐
(선택) 간단한 모달
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 / 시리즈 총정리
시리즈 목차 (실전)
- 메모 수정과 삭제
- Prisma + SQLite로 DB 붙이기
- Vercel에 배포하기
- 폼 검증하기 (zod)
- 로그인 붙이기 (Clerk)
- Neon Postgres 연결하기
- 삭제 확인 UI ← 지금
'frontend > NextJs' 카테고리의 다른 글
| # [Next.js + TS 실전] 9. FAQ · 시리즈 총정리 (1) | 2026.07.31 |
|---|---|
| # [Next.js + TS 실전] 8. 목록 검색과 정렬 (1) | 2026.07.31 |
| # [Next.js + TS 실전] 6. Neon Postgres 연결하기 (1) | 2026.07.31 |
| # [Next.js + TS 실전] 5. 로그인 붙이기 (Clerk) (0) | 2026.07.30 |
| # [Next.js + TS 실전] 4. 폼 검증하기 (zod) (0) | 2026.07.30 |
