AUDIT / src /hooks /useAutoSave.ts
Arypulka98's picture
feat(audit): deploy full backend cluster node (part 2)
cc11e77 verified
Raw
History Blame Contribute Delete
2.24 kB
/**
* useAutoSave.ts — S800: auto-salvataggio automatico per mobile
*
* Salva automaticamente in tre scenari:
* 1. Intervallo regolare (default 30s) — solo se isDirty
* 2. App va in background (visibilitychange → hidden) — CRITICO su iOS: l'app
* può essere killata dal sistema operativo in qualsiasi momento
* 3. beforeunload — best-effort (non garantito su mobile)
*
* onSave deve essere una funzione stabile (useCallback) per evitare
* re-registrazione continua degli event listener.
*
* Safari-safe: nessun uso di sendBeacon o XHR synchronous in beforeunload.
*/
import { useEffect, useRef } from "react";
interface AutoSaveOptions {
/** true se ci sono modifiche non salvate */
isDirty: boolean;
/** Funzione di salvataggio — deve essere stabile (useCallback) */
onSave: () => Promise<void>;
/** Intervallo in ms — default 30000 (30s) */
interval?: number;
/** Se false, l'auto-save è disabilitato */
enabled?: boolean;
}
export function useAutoSave({
isDirty,
onSave,
interval = 30_000,
enabled = true,
}: AutoSaveOptions): void {
const onSaveRef = useRef(onSave);
const isDirtyRef = useRef(isDirty);
// Mantieni refs aggiornate senza rimontare i listener
useEffect(() => { onSaveRef.current = onSave; }, [onSave]);
useEffect(() => { isDirtyRef.current = isDirty; }, [isDirty]);
useEffect(() => {
if (!enabled) return;
// 1. Salvataggio periodico
const id = setInterval(() => {
if (isDirtyRef.current) void onSaveRef.current();
}, interval);
// 2. App va in background — critico su iOS (viene killata se non salva)
const onVisibility = () => {
if (document.hidden && isDirtyRef.current) {
void onSaveRef.current();
}
};
document.addEventListener("visibilitychange", onVisibility, { passive: true });
// 3. beforeunload — best-effort
const onUnload = () => {
if (isDirtyRef.current) void onSaveRef.current();
};
window.addEventListener("beforeunload", onUnload);
return () => {
clearInterval(id);
document.removeEventListener("visibilitychange", onVisibility);
window.removeEventListener("beforeunload", onUnload);
};
}, [enabled, interval]);
}