Files
buzz-sheet/hooks/use-autosave.ts
T
gunshiz ac9a733370
CI / Verify (push) Successful in 57s
CI / Build immutable images and deploy (push) Successful in 3m10s
feat(guides): autosave structured guide data
2026-08-29 18:13:02 +00:00

67 lines
2.1 KiB
TypeScript

"use client";
import { useEffect, useEffectEvent, useState } from "react";
import { toast } from "sonner";
export type AutosaveState = "saved" | "unsaved" | "saving" | "incomplete" | "error";
interface SaveResult {
status: string;
message?: string;
version?: number;
}
export function useAutosave<T>({
value,
valid,
initialVersion,
persist,
delay = 900,
}: {
value: T;
valid: boolean;
initialVersion: number;
persist: (value: T, expectedVersion: number) => Promise<SaveResult>;
delay?: number;
}) {
const fingerprint = JSON.stringify(value);
const [savedFingerprint, setSavedFingerprint] = useState(fingerprint);
const [errorFingerprint, setErrorFingerprint] = useState<string | null>(null);
const [saving, setSaving] = useState(false);
const [version, setVersion] = useState(initialVersion);
const save = useEffectEvent(async (snapshot: T, snapshotFingerprint: string) => {
setSaving(true);
setErrorFingerprint(null);
try {
const result = await persist(snapshot, version);
if (result.status === "error") {
setErrorFingerprint(snapshotFingerprint);
toast.error(result.message ?? "บันทึกอัตโนมัติไม่สำเร็จ");
return;
}
if (result.version) setVersion(result.version);
setSavedFingerprint(snapshotFingerprint);
} catch (cause) {
setErrorFingerprint(snapshotFingerprint);
toast.error(cause instanceof Error ? cause.message : "บันทึกอัตโนมัติไม่สำเร็จ");
} finally {
setSaving(false);
}
});
useEffect(() => {
if (!valid || saving || fingerprint === savedFingerprint || fingerprint === errorFingerprint) return;
const timer = window.setTimeout(() => {
void save(JSON.parse(fingerprint) as T, fingerprint);
}, delay);
return () => window.clearTimeout(timer);
}, [delay, errorFingerprint, fingerprint, savedFingerprint, saving, valid]);
if (!valid) return "incomplete";
if (saving) return "saving";
if (fingerprint === errorFingerprint) return "error";
if (fingerprint !== savedFingerprint) return "unsaved";
return "saved";
}