forked from jordanlambrecht/tracker-tracker
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathuseCrudCard.ts
More file actions
95 lines (83 loc) · 2.24 KB
/
Copy pathuseCrudCard.ts
File metadata and controls
95 lines (83 loc) · 2.24 KB
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
// src/hooks/useCrudCard.ts
import { type Dispatch, type SetStateAction, useEffect, useState } from "react"
interface UseCrudCardOptions<T extends { id: number }> {
item: T
apiEndpoint: string
buildPatch: (draft: T, original: T) => Record<string, unknown> | null
onSaved: (id: number, updated: T) => void
}
interface UseCrudCardReturn<T> {
draft: T
setDraft: Dispatch<SetStateAction<T>>
updateDraft: (patch: Partial<T>) => void
dirty: boolean
saving: boolean
saveError: string | null
expanded: boolean
toggleExpand: () => void
handleSave: () => Promise<void>
handleDiscard: () => void
}
function useCrudCard<T extends { id: number }>({
item,
apiEndpoint,
buildPatch,
onSaved,
}: UseCrudCardOptions<T>): UseCrudCardReturn<T> {
const [draft, setDraft] = useState<T>(item)
const [saving, setSaving] = useState(false)
const [saveError, setSaveError] = useState<string | null>(null)
const [expanded, setExpanded] = useState(false)
const dirty = buildPatch(draft, item) !== null
// Sync draft when parent pushes new server state (and user isn't editing)
useEffect(() => {
if (!dirty) setDraft(item)
}, [item, dirty])
function updateDraft(patch: Partial<T>) {
setDraft((prev) => ({ ...prev, ...patch }))
}
async function handleSave() {
const patch = buildPatch(draft, item)
if (!patch) return
setSaving(true)
setSaveError(null)
try {
const res = await fetch(`${apiEndpoint}/${item.id}`, {
method: "PATCH",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(patch),
})
if (!res.ok) {
const data = await res.json().catch(() => ({}))
setSaveError((data as { error?: string }).error ?? "Failed to save")
return
}
onSaved(item.id, draft)
} catch {
setSaveError("Network error")
} finally {
setSaving(false)
}
}
function handleDiscard() {
setDraft(item)
setSaveError(null)
}
function toggleExpand() {
setExpanded((e) => !e)
}
return {
draft,
setDraft,
updateDraft,
dirty,
saving,
saveError,
expanded,
toggleExpand,
handleSave,
handleDiscard,
}
}
export type { UseCrudCardOptions, UseCrudCardReturn }
export { useCrudCard }