| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | ||||
| 4 | 5 | 6 | 7 | 8 | 9 | 10 |
| 11 | 12 | 13 | 14 | 15 | 16 | 17 |
| 18 | 19 | 20 | 21 | 22 | 23 | 24 |
| 25 | 26 | 27 | 28 | 29 | 30 | 31 |
Tags
- 파이썬 클래스
- 클래스 속성
- python Django
- git
- Variable declaration
- Python Class
- 강제 타입변환
- 다중조건문
- Kotlin If
- 파이썬 제어문
- 좋은글
- Kotlin 클래스
- 장고 가상환경
- 파이썬 장고
- github
- Kotlin 조건문
- Kotlin else if
- django virtualenv
- 파이썬 반복문
- Python
- activate 오류
- 도전
- 넥스트js
- 희망
- 자바 기본타입
- Kotlin 클래스 속성정의
- 파이썬
- NextJs
- 성공
- Kotlin Class
Archives
- Today
- Total
키모스토리
TanStack Query (react-query) - #2 (useMutation, 등록/수정/삭제) 본문
Web Devlopment/NextJs
TanStack Query (react-query) - #2 (useMutation, 등록/수정/삭제)
키모형 2026. 1. 18. 16:24반응형
이전 강의에 이어서 테스트를 위해 json-server 설치 (useMutation 테스트용)
json-server 설치
npm i json-server
db.json 파일 추가 (테스트용 db파일)
{
"posts": [
{
"id": 1,
"title": "첫 번째 포스트",
},
{
"id": 2,
"title": "두 번째 포스트",
}
]
}
파일 저장후 npx json-server db.json 으로 json server 실행
npx json-server db.json
JSON Server started on PORT :3000
Press CTRL-C to stop
Watching db.json...
♡( ◡‿◡ )
Index:
http://localhost:3000/
Static files:
Serving ./public directory if it exists
Endpoints:
http://localhost:3000/posts
TodoListLocal.tsx
import { useQuery } from "@tanstack/react-query";
export default function TodoListLocalPage() {
const { data, isPending, isFetching, isError, error, refetch } = useQuery({
queryKey: ["todoListLocal"], // 각 쿼리를 식별하는 고유 키
queryFn: async () => {
const response = await fetch("http://localhost:3000/posts");
return await response.json();
},
});
// 에러 상태 처리
if (isError) {
return <span>애러발생: {error.message}</span>;
}
return (
<div>
<h1>Local Todo List</h1>
<p>isPending: {isPending ? "Pending..." : "완료"}</p>
<p>isFetching: {isFetching ? "Fetching..." : "완료"}</p>
<button onClick={() => refetch()}>Refetch</button>
<ul>
{data?.map((todo: any) => (
<li key={todo.id}>
{todo.id} - {todo.title}
</li>
))}
</ul>
</div>
);
}

등록/삭제 추가
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import axios from "axios";
import { useState } from "react";
import { v4 as uuidv4 } from "uuid";
type Post = { id: string; title: string };
type PostCreate = { title: string };
const api = axios.create({
baseURL: "http://localhost:3000",
headers: { "Content-Type": "application/json" },
});
export default function TodoListLocalPage() {
const qc = useQueryClient();
const [title, setTitle] = useState("");
// 포스트 목록 조회 쿼리
const {
data = [],
isPending,
isFetching,
isError,
error,
refetch,
} = useQuery<Post[]>({
queryKey: ["posts"],
queryFn: async () => {
const res = await api.get<Post[]>("/posts");
return res.data;
},
});
// 등록함수
const addPost = async (input: PostCreate): Promise<Post> => {
// 1초 지연 시뮬레이션
await new Promise((resolve) => setTimeout(resolve, 1000));
const payload: Post = { id: uuidv4(), title: input.title.trim() };
const res = await api.post<Post>("/posts", payload);
return res.data;
};
// 등록 뮤테이션
const { mutate, isPending: isAdding } = useMutation({
mutationFn: addPost,
onSuccess: () => {
setTitle("");
// refetch() 대신 캐시 무효화 → 자동 재조회
qc.invalidateQueries({ queryKey: ["posts"] });
},
onError: (err) => {
const msg = axios.isAxiosError(err)
? (err.response?.data?.message ?? err.message)
: (err as Error).message;
console.error("등록 실패:", msg);
},
});
// 등록 폼 제출 핸들러
const submitHandler = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
const t = title.trim();
if (!t) return;
mutate({ title: t });
};
// 삭제함수
const deletePost = async (id: string): Promise<void> => {
// 1초 지연 시뮬레이션
await new Promise((resolve) => setTimeout(resolve, 1000));
await api.delete("/posts/" + id);
};
// 삭제 뮤테이션
const { mutate: deletePostMutate, isPending: isDeleting } = useMutation({
mutationFn: deletePost,
onSuccess: () => {
qc.invalidateQueries({ queryKey: ["posts"] });
},
onError: (err) => {
const msg = axios.isAxiosError(err)
? (err.response?.data?.message ?? err.message)
: (err as Error).message;
console.error("삭제 실패:", msg);
},
});
if (isError) {
return <span>에러발생: {(error as Error).message}</span>;
}
return (
<div>
<h1>Local Todo List (UUID)</h1>
<p>isPending: {isPending ? "Pending..." : "완료"}</p>
<p>isFetching: {isFetching ? "Fetching..." : "완료"}</p>
<form onSubmit={submitHandler}>
<input
type="text"
value={title}
onChange={(e) => setTitle(e.target.value)}
placeholder="새 포스트 입력"
/>
<button type="submit" disabled={isAdding}>
{isAdding ? "등록중..." : "등록"}
</button>
</form>
<hr />
<ul>
{data.map((p) => (
<li key={p.id}>
{p.id} - {p.title}
<button
onClick={() => deletePostMutate(p.id)}
disabled={isDeleting}
>
{isDeleting ? "삭제중..." : "삭제"}
</button>
</li>
))}
</ul>
</div>
);
}

반응형
'Web Devlopment > NextJs' 카테고리의 다른 글
| Zustand (0) | 2026.01.18 |
|---|---|
| TanStack Query (react-query) - #1 (useQuery, 조회) (0) | 2026.01.18 |
| Next.js Server Actions: <form action>로 CRUD 만들기 (메모리 DB 예시) (0) | 2026.01.17 |
| #37. Next.js 16 + Prisma 7 + SQLite 회원관리 예제 (0) | 2026.01.02 |
| #36. form action(Server Actions) vs onSubmit(Client Handler) (0) | 2026.01.01 |
