Skip to content

Commit edbe4ab

Browse files
committed
fix: keep local note id stable after INSERT — store dbId separately
1 parent 49d46a3 commit edbe4ab

1 file changed

Lines changed: 26 additions & 28 deletions

File tree

src/app/dashboard/page.tsx

Lines changed: 26 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,8 @@ type FilterMode = 'all' | 'encrypted' | 'plain'
222222
type SortMode = 'updated' | 'created' | 'title'
223223

224224
interface LocalNote {
225-
id: string
225+
id: string // stable local key — never changes after creation
226+
dbId?: string // real Supabase UUID — set after first INSERT
226227
title: string
227228
body: string
228229
tags: string[]
@@ -232,7 +233,6 @@ interface LocalNote {
232233
createdAt: number
233234
updatedAt: number
234235
pinned?: boolean
235-
dbId?: string
236236
}
237237

238238
export default function Dashboard() {
@@ -271,6 +271,7 @@ export default function Dashboard() {
271271

272272
const saveTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null)
273273
const isSavingRef = useRef(false)
274+
// Maps local note id → real Supabase dbId (or '' for unsaved-but-known)
274275
const persistedRef = useRef<Record<string, string>>({})
275276
const currentIdRef = useRef<string | null>(null)
276277
const notesRef = useRef<LocalNote[]>([])
@@ -364,6 +365,7 @@ export default function Dashboard() {
364365
}
365366
if (data && data.length > 0) {
366367
const mapped: LocalNote[] = data.map((n: Note) => ({
368+
// For persisted notes, local id == dbId (stable UUID from Supabase)
367369
id: n.id,
368370
dbId: n.id,
369371
title: n.title,
@@ -390,10 +392,6 @@ export default function Dashboard() {
390392
})
391393
}, [router])
392394

393-
// FIX: use stable constant IDs for sample notes so they don't
394-
// reappear after the user deletes them (uid() would generate a new
395-
// random ID on every page load, making every load look like brand-new
396-
// notes that were never deleted).
397395
function sampleNotes(): LocalNote[] {
398396
return [
399397
{
@@ -461,12 +459,16 @@ export default function Dashboard() {
461459
const titleVal = titleRef.current.trim() || 'Untitled'
462460
const encrypted_body = note.encrypted ? note.body : bodyRef.current
463461
const currentTags = tagsRef.current
464-
// persistedRef[id] is '' (empty string) for brand-new unsaved notes,
465-
// undefined for notes that were never touched, and the real dbId once
466-
// they've been inserted. The falsy check handles both '' and undefined.
462+
463+
// persistedRef[id] is '' for brand-new unsaved notes (falsy → INSERT),
464+
// and the real dbId string once persisted (truthy → UPDATE).
467465
const existingDbId = persistedRef.current[id]
468466

469467
if (!existingDbId) {
468+
// ── INSERT ──
469+
// Keep the local `id` stable in React state. Only store the real
470+
// Supabase UUID in persistedRef and note.dbId so subsequent saves
471+
// can UPDATE correctly — without ever changing note.id.
470472
const { data: inserted, error } = await supabase.from('notes').insert({
471473
user_id: user.id,
472474
title: titleVal,
@@ -482,18 +484,21 @@ export default function Dashboard() {
482484

483485
if (!error && inserted) {
484486
const dbId: string = inserted.id
485-
// Clean up the temp local id before replacing with real dbId
486-
delete persistedRef.current[id]
487-
persistedRef.current[dbId] = dbId
487+
// Map local id → real dbId so future saves take the UPDATE path
488+
persistedRef.current[id] = dbId
489+
// Patch note.dbId in state (no id change — sidebar key stays stable)
488490
setNotes(prev => prev.map(n =>
489491
n.id === id
490-
? { ...n, title: titleVal, body: encrypted_body, tags: currentTags, updatedAt: Date.now(), dbId, id: dbId }
492+
? { ...n, title: titleVal, body: encrypted_body, tags: currentTags, updatedAt: Date.now(), dbId }
491493
: n
492494
))
493-
setCurrentId(dbId)
494-
currentIdRef.current = dbId
495+
// Also update the ref immediately so any in-flight save sees dbId
496+
notesRef.current = notesRef.current.map(n =>
497+
n.id === id ? { ...n, dbId } : n
498+
)
495499
}
496500
} else {
501+
// ── UPDATE ──
497502
await supabase.from('notes').update({
498503
title: titleVal,
499504
encrypted_body,
@@ -526,16 +531,13 @@ export default function Dashboard() {
526531
}, [title, body, tags, currentId, isLocked, doSave])
527532

528533
// ── CREATE NOTE ──
529-
// FIX: seed persistedRef immediately with an empty string so doSave()
530-
// always finds a defined (falsy) entry and reliably takes the INSERT path.
531534
const createNote = () => {
532535
const note: LocalNote = {
533536
id: uid(), title: '', body: '', tags: [],
534537
encrypted: false, iv: null, salt: null, pinned: false,
535538
createdAt: Date.now(), updatedAt: Date.now(),
536539
}
537-
// Mark as "known but not yet persisted" — empty string is falsy so
538-
// doSave() will INSERT on the first save attempt.
540+
// '' is falsy → doSave will take the INSERT path on first save
539541
persistedRef.current[note.id] = ''
540542
setNotes(prev => [note, ...prev])
541543
setCurrentId(note.id)
@@ -559,7 +561,6 @@ export default function Dashboard() {
559561
updatedAt: Date.now(),
560562
pinned: false,
561563
}
562-
// Seed as unsaved so it gets INSERTed on first autosave
563564
persistedRef.current[copy.id] = ''
564565
setNotes(prev => [copy, ...prev])
565566
setCurrentId(copy.id)
@@ -573,7 +574,8 @@ export default function Dashboard() {
573574
if (!currentNote) return
574575
const newPinned = !currentNote.pinned
575576
setNotes(prev => prev.map(n => n.id === currentId ? { ...n, pinned: newPinned } : n))
576-
const dbId = persistedRef.current[currentId!]
577+
// Use note.dbId (reliable) or fall back to persistedRef
578+
const dbId = currentNote.dbId || persistedRef.current[currentId!]
577579
if (dbId) {
578580
const supabase = createClient()
579581
await supabase.from('notes').update({ pinned: newPinned }).eq('id', dbId)
@@ -584,16 +586,13 @@ export default function Dashboard() {
584586
const deleteNote = async () => {
585587
if (!currentId || !currentNote) return
586588
if (!confirm(t('deleteConfirm'))) return
587-
const dbId = persistedRef.current[currentId]
589+
// Use note.dbId (reliable) or fall back to persistedRef
590+
const dbId = currentNote.dbId || persistedRef.current[currentId]
588591
if (dbId) {
589592
const supabase = createClient()
590593
await supabase.from('notes').delete().eq('id', dbId)
591-
delete persistedRef.current[currentId]
592-
delete persistedRef.current[dbId]
593-
} else {
594-
// Remove the unsaved-marker entry too
595-
delete persistedRef.current[currentId]
596594
}
595+
delete persistedRef.current[currentId]
597596
setNotes(prev => prev.filter(n => n.id !== currentId))
598597
setCurrentId(null)
599598
setTitle('')
@@ -720,7 +719,6 @@ export default function Dashboard() {
720719
}
721720

722721
if (imported.length === 0) throw new Error('empty')
723-
// Seed each imported note as unsaved so autosave INSERTs them
724722
imported.forEach(n => { persistedRef.current[n.id] = '' })
725723
setNotes(prev => [...imported, ...prev])
726724
showToast(t('importSuccess', { n: imported.length }))

0 commit comments

Comments
 (0)