mirror of
https://github.com/Tony0410/quietthanks.git
synced 2026-05-24 21:31:41 +08:00
Fix race condition causing duplicate entries during autosave
Use refs to track entry ID and prevent concurrent saves. When a save is in progress, queue the next save instead of starting a new one. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -23,6 +23,8 @@ export function useEntry({ date, entryId, isNewEntry = false }: UseEntryOptions
|
||||
const [isLoading, setIsLoading] = useState(!isNewEntry);
|
||||
const [lastSaved, setLastSaved] = useState<Date | null>(null);
|
||||
const pendingSaveRef = useRef(false);
|
||||
const savingRef = useRef(false); // Prevent concurrent saves
|
||||
const entryIdRef = useRef<string | null>(null); // Track entry ID across saves
|
||||
|
||||
// Load entry (only if editing existing or viewing by date)
|
||||
useEffect(() => {
|
||||
@@ -45,6 +47,7 @@ export function useEntry({ date, entryId, isNewEntry = false }: UseEntryOptions
|
||||
|
||||
if (data) {
|
||||
setEntry(data);
|
||||
entryIdRef.current = data.id;
|
||||
setText(data.text);
|
||||
setMood(data.mood);
|
||||
setRoughDay(Boolean(data.roughDay));
|
||||
@@ -64,12 +67,19 @@ export function useEntry({ date, entryId, isNewEntry = false }: UseEntryOptions
|
||||
const save = useCallback(async () => {
|
||||
if (!text.trim()) return null;
|
||||
|
||||
// Prevent concurrent saves
|
||||
if (savingRef.current) {
|
||||
pendingSaveRef.current = true; // Mark that we need to save again
|
||||
return null;
|
||||
}
|
||||
|
||||
savingRef.current = true;
|
||||
setIsSaving(true);
|
||||
pendingSaveRef.current = false;
|
||||
|
||||
try {
|
||||
const body: CreateEntryRequest = {
|
||||
id: entry?.id, // Include id if updating existing
|
||||
id: entryIdRef.current || undefined, // Use ref to always have latest ID
|
||||
date: entry?.date || targetDate,
|
||||
text: text.trim(),
|
||||
mood,
|
||||
@@ -86,16 +96,28 @@ export function useEntry({ date, entryId, isNewEntry = false }: UseEntryOptions
|
||||
if (res.ok) {
|
||||
const saved: EntryWithTags = await res.json();
|
||||
setEntry(saved);
|
||||
entryIdRef.current = saved.id; // Update ref immediately
|
||||
setLastSaved(new Date());
|
||||
return saved;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to save entry:", error);
|
||||
} finally {
|
||||
savingRef.current = false;
|
||||
setIsSaving(false);
|
||||
|
||||
// If there were changes while saving, trigger another save
|
||||
if (pendingSaveRef.current) {
|
||||
pendingSaveRef.current = false;
|
||||
// Use setTimeout to avoid immediate recursion
|
||||
setTimeout(() => {
|
||||
pendingSaveRef.current = true;
|
||||
debouncedSave();
|
||||
}, 100);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}, [text, mood, roughDay, tagNames, entry?.id, entry?.date, targetDate]);
|
||||
}, [text, mood, roughDay, tagNames, entry?.date, targetDate]);
|
||||
|
||||
// Debounced save
|
||||
const debouncedSave = useDebounce(() => {
|
||||
@@ -157,12 +179,14 @@ export function useEntry({ date, entryId, isNewEntry = false }: UseEntryOptions
|
||||
// Reset form for new entry
|
||||
const reset = useCallback(() => {
|
||||
setEntry(null);
|
||||
entryIdRef.current = null; // Clear the ID ref
|
||||
setText("");
|
||||
setMood(null);
|
||||
setRoughDay(false);
|
||||
setTagNames([]);
|
||||
setLastSaved(null);
|
||||
pendingSaveRef.current = false;
|
||||
savingRef.current = false;
|
||||
}, []);
|
||||
|
||||
// Save on unmount if pending
|
||||
@@ -174,7 +198,7 @@ export function useEntry({ date, entryId, isNewEntry = false }: UseEntryOptions
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
id: entry?.id,
|
||||
id: entryIdRef.current || undefined,
|
||||
date: entry?.date || targetDate,
|
||||
text: text.trim(),
|
||||
mood,
|
||||
@@ -184,7 +208,7 @@ export function useEntry({ date, entryId, isNewEntry = false }: UseEntryOptions
|
||||
}).catch(() => {});
|
||||
}
|
||||
};
|
||||
}, [text, mood, roughDay, tagNames, entry?.id, entry?.date, targetDate]);
|
||||
}, [text, mood, roughDay, tagNames, entry?.date, targetDate]);
|
||||
|
||||
return {
|
||||
entry,
|
||||
|
||||
Reference in New Issue
Block a user