Skip to main content

@form-guardian/react

React hooks and components for form autosave. Built on top of @form-guardian/dom.

Installation

npm install @form-guardian/react

Peer Dependencies:

  • react: ^16.8.0 || ^17.0.0 || ^18.0.0

useFormAutosave(formId, options)

Main React hook for form autosave functionality.

⚠️ Stability Notice

useFormAutosave is currently unstable and may have issues with certain use cases. For production applications, consider using attachFormAutosave from @form-guardian/dom directly, which is more stable and reliable.

Import:

import { useFormAutosave } from '@form-guardian/react';

Parameters:

  • formId (string, required) - Unique identifier for the form
  • options (FormAutosaveOptions, optional) - Configuration options

Returns: UseFormAutosaveReturn

Example:

import { useFormAutosave } from '@form-guardian/react';

function MyForm() {
const {
formRef,
hasDraft,
draftTimestamp,
restoreDraft,
clearDraft,
getDraftValues,
restoreValues,
saveValues,
} = useFormAutosave('my-form', {
autoRestore: true,
debounceMs: 300,
batchSaveInterval: 5000, // Save changes in batches every 5 seconds
onBeforeSave: async (values) => {
console.log('About to save:', values);
},
onAfterSave: async (values) => {
console.log('Saved successfully:', values);
},
});

return (
<form ref={formRef}>
{hasDraft && (
<div className="draft-banner">
Draft saved at: {new Date(draftTimestamp).toLocaleString()}
</div>
)}
{/* form fields */}
</form>
);
}

UseFormAutosaveReturn

Object returned by useFormAutosave() hook.

Properties

formRef: RefObject<HTMLFormElement>

Ref to attach to your form element. Required.

<form ref={formRef}>...</form>

hasDraft: boolean

Whether a draft exists for this form.

{hasDraft && <div>You have unsaved changes</div>}

draftTimestamp: number | null

Timestamp when the draft was last updated, or null if no draft exists.

{draftTimestamp && (
<div>Last saved: {new Date(draftTimestamp).toLocaleString()}</div>
)}

Methods

restoreDraft(): Promise<void>

Manually restore draft to form. Usually not needed if autoRestore: true.

<button onClick={() => restoreDraft()}>Restore Draft</button>

clearDraft(): Promise<void>

Clear draft from storage.

const handleSubmit = async (e) => {
e.preventDefault();
await clearDraft();
// Submit form...
};

getDraftValues(): Promise<T | null>

Get draft values as an object without restoring to form.

const values = await getDraftValues();
console.log(values); // { name: 'John', email: 'john@example.com' }

restoreValues(setValue, getValues): Promise<void>

Restore values for controlled components (React Hook Form, Formik, etc.).

// React Hook Form example
const { setValue, getValues } = useForm();

const handleRestore = async () => {
await restoreValues(setValue, getValues);
};

saveValues(): Promise<void>

Manually save current form values. Useful for controlled forms.

<button onClick={() => saveValues()}>Save Draft</button>

useDraftStatus(formId, options)

Lightweight hook to get draft status without DOM interaction. Perfect for showing draft indicators outside the form component.

Import:

import { useDraftStatus } from '@form-guardian/react';

Parameters:

  • formId (string, required) - Unique identifier for the form
  • options (UseDraftStatusOptions, optional) - Configuration options

Returns: UseDraftStatusResult

Example:

import { useDraftStatus } from '@form-guardian/react';

function DraftIndicator() {
const {
hasDraft,
updatedAt,
isExpired,
isChecking,
refresh,
clear
} = useDraftStatus('my-form', {
ttl: { days: 7 },
});

if (isChecking) return <div>Checking...</div>;
if (!hasDraft) return null;
if (isExpired) return <div>Draft expired</div>;

return (
<div>
Draft saved at: {new Date(updatedAt).toLocaleString()}
<button onClick={clear}>Clear</button>
<button onClick={refresh}>Refresh</button>
</div>
);
}

UseDraftStatusResult

Object returned by useDraftStatus() hook.

Properties

hasDraft: boolean

Whether a draft exists.

isExpired: boolean

Whether the draft has expired (based on TTL).

updatedAt: number | null

Timestamp when draft was last updated.

isChecking: boolean

Whether the status is currently being checked.

Methods

refresh(): Promise<void>

Manually refresh draft status from storage.

clear(): Promise<void>

Clear draft from storage.

Use Cases

Uncontrolled Form

import { useFormAutosave } from '@form-guardian/react';

function ContactForm() {
const { formRef, hasDraft, clearDraft } = useFormAutosave('contact-form', {
autoRestore: true,
debounceMs: 500,
});

const handleSubmit = async (e) => {
e.preventDefault();
await clearDraft();
// Submit form...
};

return (
<form ref={formRef} onSubmit={handleSubmit}>
{hasDraft && <div className="alert">Unsaved changes detected</div>}
<input name="name" placeholder="Name" />
<input name="email" type="email" placeholder="Email" />
<button type="submit">Submit</button>
</form>
);
}

React Hook Form (Controlled)

import { useForm } from 'react-hook-form';
import { useFormAutosave } from '@form-guardian/react';

function MyForm() {
const { register, handleSubmit, setValue, getValues } = useForm();

const { formRef, restoreValues, clearDraft, saveValues } = useFormAutosave(
'my-form',
{
autoRestore: false, // Manual restore for controlled forms
debounceMs: 300,
}
);

// Restore draft on mount
useEffect(() => {
restoreValues(setValue, getValues);
}, []);

// Auto-save on field changes
const { watch } = useForm();
useEffect(() => {
const subscription = watch(() => {
saveValues();
});
return () => subscription.unsubscribe();
}, [watch]);

const onSubmit = async (data) => {
await clearDraft();
// Submit data...
};

return (
<form ref={formRef} onSubmit={handleSubmit(onSubmit)}>
<input {...register('name')} />
<input {...register('email')} />
<button type="submit">Submit</button>
</form>
);
}

Draft Status Indicator (Separate Component)

import { useDraftStatus } from '@form-guardian/react';

function Header() {
const { hasDraft, updatedAt } = useDraftStatus('contact-form');

return (
<header>
<h1>My App</h1>
{hasDraft && (
<div className="draft-badge">
📝 Unsaved draft from {new Date(updatedAt).toLocaleTimeString()}
</div>
)}
</header>
);
}