@kood/claude-code 0.1.6 → 0.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +21 -243
- package/package.json +1 -1
- package/templates/hono/CLAUDE.md +10 -6
- package/templates/hono/docs/deployment/index.md +5 -0
- package/templates/hono/docs/library/hono/index.md +6 -0
- package/templates/hono/docs/library/prisma/index.md +3 -0
- package/templates/npx/CLAUDE.md +8 -2
- package/templates/tanstack-start/CLAUDE.md +103 -255
- package/templates/tanstack-start/docs/deployment/cloudflare.md +37 -424
- package/templates/tanstack-start/docs/deployment/index.md +57 -286
- package/templates/tanstack-start/docs/deployment/nitro.md +36 -318
- package/templates/tanstack-start/docs/deployment/railway.md +40 -409
- package/templates/tanstack-start/docs/deployment/vercel.md +43 -465
- package/templates/tanstack-start/docs/design/accessibility.md +56 -326
- package/templates/tanstack-start/docs/design/color.md +37 -179
- package/templates/tanstack-start/docs/design/components.md +77 -311
- package/templates/tanstack-start/docs/design/index.md +24 -87
- package/templates/tanstack-start/docs/design/safe-area.md +51 -250
- package/templates/tanstack-start/docs/design/spacing.md +57 -276
- package/templates/tanstack-start/docs/design/tailwind-setup.md +45 -359
- package/templates/tanstack-start/docs/design/typography.md +40 -284
- package/templates/tanstack-start/docs/library/better-auth/2fa.md +27 -115
- package/templates/tanstack-start/docs/library/better-auth/advanced.md +22 -105
- package/templates/tanstack-start/docs/library/better-auth/index.md +17 -66
- package/templates/tanstack-start/docs/library/better-auth/plugins.md +11 -88
- package/templates/tanstack-start/docs/library/better-auth/session.md +12 -92
- package/templates/tanstack-start/docs/library/better-auth/setup.md +9 -91
- package/templates/tanstack-start/docs/library/prisma/cloudflare-d1.md +30 -358
- package/templates/tanstack-start/docs/library/prisma/config.md +27 -327
- package/templates/tanstack-start/docs/library/prisma/crud.md +46 -174
- package/templates/tanstack-start/docs/library/prisma/index.md +23 -113
- package/templates/tanstack-start/docs/library/prisma/relations.md +31 -153
- package/templates/tanstack-start/docs/library/prisma/schema.md +40 -217
- package/templates/tanstack-start/docs/library/prisma/setup.md +12 -112
- package/templates/tanstack-start/docs/library/prisma/transactions.md +20 -110
- package/templates/tanstack-start/docs/library/tanstack-query/index.md +12 -99
- package/templates/tanstack-start/docs/library/tanstack-query/invalidation.md +28 -107
- package/templates/tanstack-start/docs/library/tanstack-query/optimistic-updates.md +44 -146
- package/templates/tanstack-start/docs/library/tanstack-query/setup.md +11 -70
- package/templates/tanstack-start/docs/library/tanstack-query/use-mutation.md +33 -127
- package/templates/tanstack-start/docs/library/tanstack-query/use-query.md +49 -149
- package/templates/tanstack-start/docs/library/tanstack-start/auth-patterns.md +19 -112
- package/templates/tanstack-start/docs/library/tanstack-start/index.md +33 -80
- package/templates/tanstack-start/docs/library/tanstack-start/middleware.md +28 -106
- package/templates/tanstack-start/docs/library/tanstack-start/routing.md +21 -118
- package/templates/tanstack-start/docs/library/tanstack-start/server-functions.md +34 -246
- package/templates/tanstack-start/docs/library/tanstack-start/setup.md +6 -39
- package/templates/tanstack-start/docs/library/zod/basic-types.md +33 -145
- package/templates/tanstack-start/docs/library/zod/complex-types.md +32 -156
- package/templates/tanstack-start/docs/library/zod/index.md +22 -150
- package/templates/tanstack-start/docs/library/zod/transforms.md +20 -129
- package/templates/tanstack-start/docs/library/zod/validation.md +39 -155
- package/templates/hono/docs/commands/git.md +0 -145
- package/templates/hono/docs/mcp/context7.md +0 -106
- package/templates/hono/docs/mcp/index.md +0 -176
- package/templates/hono/docs/mcp/sequential-thinking.md +0 -101
- package/templates/hono/docs/mcp/serena.md +0 -269
- package/templates/hono/docs/mcp/sgrep.md +0 -105
- package/templates/hono/docs/skills/gemini-review/SKILL.md +0 -220
- package/templates/hono/docs/skills/gemini-review/references/checklists.md +0 -136
- package/templates/hono/docs/skills/gemini-review/references/prompt-templates.md +0 -303
- package/templates/npx/docs/commands/git.md +0 -145
- package/templates/npx/docs/mcp/index.md +0 -60
- package/templates/npx/docs/skills/gemini-review/SKILL.md +0 -220
- package/templates/npx/docs/skills/gemini-review/references/checklists.md +0 -134
- package/templates/npx/docs/skills/gemini-review/references/prompt-templates.md +0 -301
- package/templates/tanstack-start/docs/commands/git.md +0 -145
- package/templates/tanstack-start/docs/mcp/context7.md +0 -204
- package/templates/tanstack-start/docs/mcp/index.md +0 -177
- package/templates/tanstack-start/docs/mcp/sequential-thinking.md +0 -180
- package/templates/tanstack-start/docs/mcp/serena.md +0 -269
- package/templates/tanstack-start/docs/mcp/sgrep.md +0 -174
- package/templates/tanstack-start/docs/skills/gemini-review/SKILL.md +0 -220
- package/templates/tanstack-start/docs/skills/gemini-review/references/checklists.md +0 -144
- package/templates/tanstack-start/docs/skills/gemini-review/references/prompt-templates.md +0 -292
|
@@ -1,196 +1,94 @@
|
|
|
1
1
|
# TanStack Query - Optimistic Updates
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
서버 응답 전 UI 즉시 업데이트.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
## 기본 패턴
|
|
8
|
-
|
|
9
|
-
```tsx
|
|
10
|
-
const addTodoMutation = useMutation({
|
|
11
|
-
mutationFn: (newTodo: string) => axios.post('/api/data', { text: newTodo }),
|
|
12
|
-
// mutation 완료 후 쿼리 무효화
|
|
13
|
-
onSettled: () => queryClient.invalidateQueries({ queryKey: ['todos'] }),
|
|
14
|
-
})
|
|
15
|
-
|
|
16
|
-
const { isPending, submittedAt, variables, mutate, isError } = addTodoMutation
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
## 완전한 Optimistic Update 패턴
|
|
20
|
-
|
|
21
|
-
```tsx
|
|
22
|
-
type Todo = { id: string; text: string }
|
|
23
|
-
type TodosResponse = { items: Todo[]; ts: number }
|
|
24
|
-
|
|
25
|
-
function TodoList() {
|
|
26
|
-
const queryClient = useQueryClient()
|
|
27
|
-
const [text, setText] = React.useState('')
|
|
28
|
-
|
|
29
|
-
const addTodoMutation = useMutation({
|
|
30
|
-
mutationFn: async (newTodo: string): Promise<Todo> => {
|
|
31
|
-
const response = await fetch('/api/todos', {
|
|
32
|
-
method: 'POST',
|
|
33
|
-
headers: { 'Content-Type': 'application/json' },
|
|
34
|
-
body: JSON.stringify({ text: newTodo }),
|
|
35
|
-
})
|
|
36
|
-
return response.json()
|
|
37
|
-
},
|
|
38
|
-
onMutate: async (newTodo) => {
|
|
39
|
-
// 1. 진행 중인 refetch 취소 (optimistic update 덮어쓰기 방지)
|
|
40
|
-
await queryClient.cancelQueries({ queryKey: ['todos'] })
|
|
41
|
-
|
|
42
|
-
// 2. 롤백을 위한 이전 값 스냅샷
|
|
43
|
-
const previousTodos = queryClient.getQueryData<TodosResponse>(['todos'])
|
|
44
|
-
|
|
45
|
-
// 3. 캐시 optimistic 업데이트
|
|
46
|
-
if (previousTodos) {
|
|
47
|
-
queryClient.setQueryData<TodosResponse>(['todos'], {
|
|
48
|
-
...previousTodos,
|
|
49
|
-
items: [
|
|
50
|
-
...previousTodos.items,
|
|
51
|
-
{ id: Math.random().toString(), text: newTodo },
|
|
52
|
-
],
|
|
53
|
-
})
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// 4. context로 이전 값 반환
|
|
57
|
-
return { previousTodos }
|
|
58
|
-
},
|
|
59
|
-
onError: (err, variables, context) => {
|
|
60
|
-
// 5. 에러 시 롤백
|
|
61
|
-
if (context?.previousTodos) {
|
|
62
|
-
queryClient.setQueryData(['todos'], context.previousTodos)
|
|
63
|
-
}
|
|
64
|
-
},
|
|
65
|
-
onSettled: () => {
|
|
66
|
-
// 6. 서버와 캐시 동기화를 위해 refetch
|
|
67
|
-
queryClient.invalidateQueries({ queryKey: ['todos'] })
|
|
68
|
-
},
|
|
69
|
-
})
|
|
70
|
-
|
|
71
|
-
return (
|
|
72
|
-
<div>
|
|
73
|
-
<form onSubmit={(e) => {
|
|
74
|
-
e.preventDefault()
|
|
75
|
-
addTodoMutation.mutate(text)
|
|
76
|
-
setText('')
|
|
77
|
-
}}>
|
|
78
|
-
<input value={text} onChange={(e) => setText(e.target.value)} />
|
|
79
|
-
<button type="submit" disabled={addTodoMutation.isPending}>
|
|
80
|
-
{addTodoMutation.isPending ? 'Adding...' : 'Add Todo'}
|
|
81
|
-
</button>
|
|
82
|
-
</form>
|
|
83
|
-
{addTodoMutation.isError && <div>Error: {addTodoMutation.error.message}</div>}
|
|
84
|
-
</div>
|
|
85
|
-
)
|
|
86
|
-
}
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
## 단일 항목 Optimistic Update
|
|
5
|
+
## 패턴
|
|
90
6
|
|
|
91
7
|
```tsx
|
|
92
8
|
useMutation({
|
|
93
|
-
mutationFn:
|
|
9
|
+
mutationFn: addTodo,
|
|
94
10
|
onMutate: async (newTodo) => {
|
|
95
|
-
// 진행 중인 refetch 취소
|
|
96
|
-
await queryClient.cancelQueries({ queryKey: ['todos'
|
|
11
|
+
// 1. 진행 중인 refetch 취소
|
|
12
|
+
await queryClient.cancelQueries({ queryKey: ['todos'] })
|
|
97
13
|
|
|
98
|
-
// 이전 값 스냅샷
|
|
99
|
-
const
|
|
14
|
+
// 2. 이전 값 스냅샷
|
|
15
|
+
const previousTodos = queryClient.getQueryData(['todos'])
|
|
100
16
|
|
|
101
|
-
//
|
|
102
|
-
queryClient.setQueryData(['todos',
|
|
17
|
+
// 3. 낙관적 업데이트
|
|
18
|
+
queryClient.setQueryData(['todos'], (old) => [...old, newTodo])
|
|
103
19
|
|
|
104
|
-
|
|
20
|
+
// 4. context로 이전 값 반환
|
|
21
|
+
return { previousTodos }
|
|
105
22
|
},
|
|
106
23
|
onError: (err, newTodo, context) => {
|
|
107
|
-
// 에러 시 롤백
|
|
108
|
-
queryClient.setQueryData(
|
|
109
|
-
['todos', context.newTodo.id],
|
|
110
|
-
context.previousTodo,
|
|
111
|
-
)
|
|
24
|
+
// 5. 에러 시 롤백
|
|
25
|
+
queryClient.setQueryData(['todos'], context.previousTodos)
|
|
112
26
|
},
|
|
113
|
-
onSettled: (
|
|
114
|
-
|
|
27
|
+
onSettled: () => {
|
|
28
|
+
// 6. 서버와 동기화
|
|
29
|
+
queryClient.invalidateQueries({ queryKey: ['todos'] })
|
|
115
30
|
},
|
|
116
31
|
})
|
|
117
32
|
```
|
|
118
33
|
|
|
119
|
-
## 삭제
|
|
34
|
+
## 삭제
|
|
120
35
|
|
|
121
36
|
```tsx
|
|
122
|
-
|
|
123
|
-
mutationFn:
|
|
37
|
+
useMutation({
|
|
38
|
+
mutationFn: deleteTodo,
|
|
124
39
|
onMutate: async (todoId) => {
|
|
125
40
|
await queryClient.cancelQueries({ queryKey: ['todos'] })
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
// 삭제된 항목 제외
|
|
130
|
-
queryClient.setQueryData<Todo[]>(['todos'], (old) =>
|
|
131
|
-
old?.filter((todo) => todo.id !== todoId)
|
|
41
|
+
const previousTodos = queryClient.getQueryData(['todos'])
|
|
42
|
+
queryClient.setQueryData(['todos'], (old) =>
|
|
43
|
+
old.filter((todo) => todo.id !== todoId)
|
|
132
44
|
)
|
|
133
|
-
|
|
134
45
|
return { previousTodos }
|
|
135
46
|
},
|
|
136
47
|
onError: (err, todoId, context) => {
|
|
137
|
-
queryClient.setQueryData(['todos'], context
|
|
138
|
-
},
|
|
139
|
-
onSettled: () => {
|
|
140
|
-
queryClient.invalidateQueries({ queryKey: ['todos'] })
|
|
48
|
+
queryClient.setQueryData(['todos'], context.previousTodos)
|
|
141
49
|
},
|
|
50
|
+
onSettled: () => queryClient.invalidateQueries({ queryKey: ['todos'] }),
|
|
142
51
|
})
|
|
143
52
|
```
|
|
144
53
|
|
|
145
|
-
## 토글
|
|
54
|
+
## 토글
|
|
146
55
|
|
|
147
56
|
```tsx
|
|
148
|
-
|
|
149
|
-
mutationFn:
|
|
57
|
+
useMutation({
|
|
58
|
+
mutationFn: toggleTodo,
|
|
150
59
|
onMutate: async (todoId) => {
|
|
151
60
|
await queryClient.cancelQueries({ queryKey: ['todos'] })
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
queryClient.setQueryData<Todo[]>(['todos'], (old) =>
|
|
157
|
-
old?.map((todo) =>
|
|
158
|
-
todo.id === todoId
|
|
159
|
-
? { ...todo, completed: !todo.completed }
|
|
160
|
-
: todo
|
|
61
|
+
const previousTodos = queryClient.getQueryData(['todos'])
|
|
62
|
+
queryClient.setQueryData(['todos'], (old) =>
|
|
63
|
+
old.map((todo) =>
|
|
64
|
+
todo.id === todoId ? { ...todo, completed: !todo.completed } : todo
|
|
161
65
|
)
|
|
162
66
|
)
|
|
163
|
-
|
|
164
67
|
return { previousTodos }
|
|
165
68
|
},
|
|
166
69
|
onError: (err, todoId, context) => {
|
|
167
|
-
queryClient.setQueryData(['todos'], context
|
|
168
|
-
},
|
|
169
|
-
onSettled: () => {
|
|
170
|
-
queryClient.invalidateQueries({ queryKey: ['todos'] })
|
|
70
|
+
queryClient.setQueryData(['todos'], context.previousTodos)
|
|
171
71
|
},
|
|
72
|
+
onSettled: () => queryClient.invalidateQueries({ queryKey: ['todos'] }),
|
|
172
73
|
})
|
|
173
74
|
```
|
|
174
75
|
|
|
175
|
-
##
|
|
176
|
-
|
|
177
|
-
### Optimistic Update 즉시 저장
|
|
76
|
+
## 단일 항목 업데이트
|
|
178
77
|
|
|
179
78
|
```tsx
|
|
180
|
-
const persister = experimental_createQueryPersister({
|
|
181
|
-
storage: AsyncStorage,
|
|
182
|
-
maxAge: 1000 * 60 * 60 * 12, // 12시간
|
|
183
|
-
})
|
|
184
|
-
|
|
185
|
-
const queryClient = useQueryClient()
|
|
186
|
-
|
|
187
79
|
useMutation({
|
|
188
80
|
mutationFn: updateTodo,
|
|
189
81
|
onMutate: async (newTodo) => {
|
|
190
|
-
|
|
191
|
-
queryClient.
|
|
192
|
-
|
|
193
|
-
|
|
82
|
+
await queryClient.cancelQueries({ queryKey: ['todos', newTodo.id] })
|
|
83
|
+
const previousTodo = queryClient.getQueryData(['todos', newTodo.id])
|
|
84
|
+
queryClient.setQueryData(['todos', newTodo.id], newTodo)
|
|
85
|
+
return { previousTodo, newTodo }
|
|
86
|
+
},
|
|
87
|
+
onError: (err, newTodo, context) => {
|
|
88
|
+
queryClient.setQueryData(['todos', context.newTodo.id], context.previousTodo)
|
|
89
|
+
},
|
|
90
|
+
onSettled: (newTodo) => {
|
|
91
|
+
queryClient.invalidateQueries({ queryKey: ['todos', newTodo.id] })
|
|
194
92
|
},
|
|
195
93
|
})
|
|
196
94
|
```
|
|
@@ -1,93 +1,38 @@
|
|
|
1
1
|
# TanStack Query - 설치 및 설정
|
|
2
2
|
|
|
3
|
-
> **상위 문서**: [TanStack Query](./index.md)
|
|
4
|
-
|
|
5
3
|
## 설치
|
|
6
4
|
|
|
7
5
|
```bash
|
|
8
6
|
yarn add @tanstack/react-query
|
|
9
7
|
```
|
|
10
8
|
|
|
11
|
-
## QueryClient 설정
|
|
12
|
-
|
|
13
|
-
```tsx
|
|
14
|
-
import {
|
|
15
|
-
QueryClient,
|
|
16
|
-
QueryClientProvider,
|
|
17
|
-
} from '@tanstack/react-query'
|
|
18
|
-
|
|
19
|
-
// QueryClient 생성
|
|
20
|
-
const queryClient = new QueryClient()
|
|
21
|
-
|
|
22
|
-
function App() {
|
|
23
|
-
return (
|
|
24
|
-
<QueryClientProvider client={queryClient}>
|
|
25
|
-
<YourApp />
|
|
26
|
-
</QueryClientProvider>
|
|
27
|
-
)
|
|
28
|
-
}
|
|
29
|
-
```
|
|
30
|
-
|
|
31
9
|
## QueryClient 옵션
|
|
32
10
|
|
|
33
11
|
```tsx
|
|
34
12
|
const queryClient = new QueryClient({
|
|
35
13
|
defaultOptions: {
|
|
36
14
|
queries: {
|
|
37
|
-
staleTime: 1000 * 60 * 5,
|
|
38
|
-
gcTime: 1000 * 60 * 30,
|
|
15
|
+
staleTime: 1000 * 60 * 5, // 5분
|
|
16
|
+
gcTime: 1000 * 60 * 30, // 30분 (이전 cacheTime)
|
|
39
17
|
retry: 3,
|
|
40
|
-
retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 30000),
|
|
41
18
|
refetchOnWindowFocus: true,
|
|
42
19
|
refetchOnReconnect: true,
|
|
43
20
|
},
|
|
44
|
-
mutations: {
|
|
45
|
-
retry: 1,
|
|
46
|
-
},
|
|
21
|
+
mutations: { retry: 1 },
|
|
47
22
|
},
|
|
48
23
|
})
|
|
49
24
|
```
|
|
50
25
|
|
|
51
|
-
## TanStack Start와 함께 사용
|
|
52
|
-
|
|
53
|
-
```tsx
|
|
54
|
-
import { useQuery } from '@tanstack/react-query'
|
|
55
|
-
import { getServerPosts } from '@/lib/server-functions'
|
|
56
|
-
|
|
57
|
-
function PostList() {
|
|
58
|
-
const { data, isLoading, error } = useQuery({
|
|
59
|
-
queryKey: ['posts'],
|
|
60
|
-
queryFn: () => getServerPosts(),
|
|
61
|
-
})
|
|
62
|
-
|
|
63
|
-
if (isLoading) return <div>Loading...</div>
|
|
64
|
-
if (error) return <div>Error: {error.message}</div>
|
|
65
|
-
|
|
66
|
-
return (
|
|
67
|
-
<ul>
|
|
68
|
-
{data?.map((post) => (
|
|
69
|
-
<li key={post.id}>{post.title}</li>
|
|
70
|
-
))}
|
|
71
|
-
</ul>
|
|
72
|
-
)
|
|
73
|
-
}
|
|
74
|
-
```
|
|
75
|
-
|
|
76
26
|
## Query Keys 패턴
|
|
77
27
|
|
|
78
28
|
```typescript
|
|
79
|
-
//
|
|
80
|
-
['
|
|
81
|
-
|
|
82
|
-
// 파라미터가 있는 키
|
|
83
|
-
['todo', { id: 5 }]
|
|
84
|
-
|
|
85
|
-
// 계층적 키
|
|
86
|
-
['todos', 'list', { filters: 'all' }]
|
|
29
|
+
['todos'] // 단순
|
|
30
|
+
['todo', { id: 5 }] // 파라미터
|
|
31
|
+
['todos', 'list', { filters }] // 계층적
|
|
87
32
|
['todos', 'detail', todoId]
|
|
88
33
|
```
|
|
89
34
|
|
|
90
|
-
## DevTools
|
|
35
|
+
## DevTools
|
|
91
36
|
|
|
92
37
|
```bash
|
|
93
38
|
yarn add @tanstack/react-query-devtools
|
|
@@ -96,12 +41,8 @@ yarn add @tanstack/react-query-devtools
|
|
|
96
41
|
```tsx
|
|
97
42
|
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
|
|
98
43
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
<ReactQueryDevtools initialIsOpen={false} />
|
|
104
|
-
</QueryClientProvider>
|
|
105
|
-
)
|
|
106
|
-
}
|
|
44
|
+
<QueryClientProvider client={queryClient}>
|
|
45
|
+
<YourApp />
|
|
46
|
+
<ReactQueryDevtools initialIsOpen={false} />
|
|
47
|
+
</QueryClientProvider>
|
|
107
48
|
```
|
|
@@ -1,103 +1,48 @@
|
|
|
1
1
|
# TanStack Query - useMutation
|
|
2
2
|
|
|
3
|
-
> **상위 문서**: [TanStack Query](./index.md)
|
|
4
|
-
|
|
5
3
|
## 기본 사용법
|
|
6
4
|
|
|
7
5
|
```tsx
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
function AddTodo() {
|
|
11
|
-
const queryClient = useQueryClient()
|
|
12
|
-
|
|
13
|
-
const mutation = useMutation({
|
|
14
|
-
mutationFn: postTodo,
|
|
15
|
-
onSuccess: () => {
|
|
16
|
-
// 성공 시 todos 쿼리 무효화하여 재조회
|
|
17
|
-
queryClient.invalidateQueries({ queryKey: ['todos'] })
|
|
18
|
-
},
|
|
19
|
-
})
|
|
6
|
+
const queryClient = useQueryClient()
|
|
20
7
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
id: Date.now(),
|
|
26
|
-
title: 'Do Laundry',
|
|
27
|
-
})
|
|
28
|
-
}}
|
|
29
|
-
>
|
|
30
|
-
Add Todo
|
|
31
|
-
</button>
|
|
32
|
-
)
|
|
33
|
-
}
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
## useMutation 반환 값
|
|
8
|
+
const mutation = useMutation({
|
|
9
|
+
mutationFn: postTodo,
|
|
10
|
+
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['todos'] }),
|
|
11
|
+
})
|
|
37
12
|
|
|
38
|
-
|
|
39
|
-
const {
|
|
40
|
-
data, // mutation 결과 데이터
|
|
41
|
-
error, // 에러 객체
|
|
42
|
-
isError, // 에러 상태
|
|
43
|
-
isIdle, // 아직 실행 안 됨
|
|
44
|
-
isPending, // 실행 중
|
|
45
|
-
isPaused, // 일시 정지됨
|
|
46
|
-
isSuccess, // 성공 상태
|
|
47
|
-
failureCount, // 실패 횟수
|
|
48
|
-
failureReason, // 실패 이유
|
|
49
|
-
mutate, // mutation 실행 (비동기)
|
|
50
|
-
mutateAsync, // mutation 실행 (Promise 반환)
|
|
51
|
-
reset, // 상태 초기화
|
|
52
|
-
status, // 'idle' | 'pending' | 'success' | 'error'
|
|
53
|
-
submittedAt, // 제출 시간
|
|
54
|
-
variables, // 전달된 변수
|
|
55
|
-
} = useMutation({ mutationFn })
|
|
13
|
+
mutation.mutate({ title: 'New Todo' })
|
|
56
14
|
```
|
|
57
15
|
|
|
58
|
-
##
|
|
16
|
+
## 반환값
|
|
59
17
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
onSuccess, // 성공 콜백
|
|
71
|
-
retry, // 재시도 횟수
|
|
72
|
-
retryDelay, // 재시도 딜레이
|
|
73
|
-
scope, // 범위
|
|
74
|
-
throwOnError, // 에러 throw 여부
|
|
75
|
-
})
|
|
76
|
-
```
|
|
18
|
+
| 속성 | 설명 |
|
|
19
|
+
|------|------|
|
|
20
|
+
| data | mutation 결과 |
|
|
21
|
+
| error | 에러 객체 |
|
|
22
|
+
| isPending | 실행 중 |
|
|
23
|
+
| isSuccess/isError | 상태 |
|
|
24
|
+
| mutate | 실행 (비동기) |
|
|
25
|
+
| mutateAsync | 실행 (Promise) |
|
|
26
|
+
| reset | 상태 초기화 |
|
|
27
|
+
| variables | 전달된 변수 |
|
|
77
28
|
|
|
78
|
-
## 콜백
|
|
29
|
+
## 콜백
|
|
79
30
|
|
|
80
31
|
```tsx
|
|
81
|
-
|
|
32
|
+
useMutation({
|
|
82
33
|
mutationFn: updateTodo,
|
|
83
34
|
onMutate: async (newTodo) => {
|
|
84
|
-
// mutation 시작 전
|
|
85
|
-
|
|
86
|
-
return { previousData: 'something' } // context로 전달
|
|
35
|
+
// mutation 시작 전 (optimistic update용)
|
|
36
|
+
return { previousData } // context로 전달
|
|
87
37
|
},
|
|
88
38
|
onSuccess: (data, variables, context) => {
|
|
89
|
-
// 성공 시
|
|
90
|
-
console.log('Success:', data)
|
|
91
|
-
console.log('Variables:', variables)
|
|
92
|
-
console.log('Context:', context)
|
|
39
|
+
// 성공 시
|
|
93
40
|
},
|
|
94
41
|
onError: (error, variables, context) => {
|
|
95
|
-
// 에러 시
|
|
96
|
-
console.log('Error:', error)
|
|
97
|
-
// context로 롤백 가능
|
|
42
|
+
// 에러 시 (context로 롤백)
|
|
98
43
|
},
|
|
99
44
|
onSettled: (data, error, variables, context) => {
|
|
100
|
-
//
|
|
45
|
+
// 완료 시 (성공/실패 무관)
|
|
101
46
|
queryClient.invalidateQueries({ queryKey: ['todos'] })
|
|
102
47
|
},
|
|
103
48
|
})
|
|
@@ -106,65 +51,26 @@ const mutation = useMutation({
|
|
|
106
51
|
## mutate vs mutateAsync
|
|
107
52
|
|
|
108
53
|
```tsx
|
|
109
|
-
//
|
|
54
|
+
// 콜백 기반
|
|
110
55
|
mutation.mutate(data, {
|
|
111
|
-
onSuccess: (result) =>
|
|
112
|
-
|
|
113
|
-
},
|
|
114
|
-
onError: (error) => {
|
|
115
|
-
console.log('Error:', error)
|
|
116
|
-
},
|
|
56
|
+
onSuccess: (result) => console.log(result),
|
|
57
|
+
onError: (error) => console.log(error),
|
|
117
58
|
})
|
|
118
59
|
|
|
119
|
-
//
|
|
60
|
+
// Promise 기반
|
|
120
61
|
try {
|
|
121
62
|
const result = await mutation.mutateAsync(data)
|
|
122
|
-
|
|
123
|
-
} catch (error) {
|
|
124
|
-
console.log('Error:', error)
|
|
125
|
-
}
|
|
63
|
+
} catch (error) { ... }
|
|
126
64
|
```
|
|
127
65
|
|
|
128
|
-
##
|
|
66
|
+
## 캐시 업데이트
|
|
129
67
|
|
|
130
68
|
```tsx
|
|
131
|
-
|
|
132
|
-
const [title, setTitle] = useState('')
|
|
133
|
-
const mutation = useMutation({ mutationFn: createTodo })
|
|
134
|
-
|
|
135
|
-
const onCreateTodo = (e) => {
|
|
136
|
-
e.preventDefault()
|
|
137
|
-
mutation.mutate({ title })
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
return (
|
|
141
|
-
<form onSubmit={onCreateTodo}>
|
|
142
|
-
{mutation.error && (
|
|
143
|
-
<h5 onClick={() => mutation.reset()}>{mutation.error}</h5>
|
|
144
|
-
)}
|
|
145
|
-
<input
|
|
146
|
-
type="text"
|
|
147
|
-
value={title}
|
|
148
|
-
onChange={(e) => setTitle(e.target.value)}
|
|
149
|
-
/>
|
|
150
|
-
<br />
|
|
151
|
-
<button type="submit">Create Todo</button>
|
|
152
|
-
</form>
|
|
153
|
-
)
|
|
154
|
-
}
|
|
155
|
-
```
|
|
156
|
-
|
|
157
|
-
## 캐시 직접 업데이트
|
|
158
|
-
|
|
159
|
-
```tsx
|
|
160
|
-
const saveMutation = useMutation({
|
|
69
|
+
useMutation({
|
|
161
70
|
mutationFn: patchTodo,
|
|
162
71
|
onSuccess: (data) => {
|
|
163
|
-
// 목록
|
|
164
|
-
queryClient.
|
|
165
|
-
|
|
166
|
-
// 개별 항목 캐시 직접 업데이트
|
|
167
|
-
queryClient.setQueryData(['todo', { id: data.id }], data)
|
|
72
|
+
queryClient.invalidateQueries({ queryKey: ['todos'] }) // 목록 무효화
|
|
73
|
+
queryClient.setQueryData(['todo', { id: data.id }], data) // 개별 캐시 업데이트
|
|
168
74
|
},
|
|
169
75
|
})
|
|
170
76
|
```
|