Skip to main content

Draft Status Hook

Lightweight React hook to track draft status without DOM interaction.

Overview

useDraftStatus provides:

  • Real-time draft status tracking
  • No DOM manipulation (read-only)
  • Expiration detection
  • Manual refresh capability

Why useDraftStatus?

Unlike useFormAutosave, useDraftStatus:

  • ✅ Doesn't attach to DOM elements
  • ✅ Doesn't save or restore drafts
  • ✅ Only reads from storage
  • ✅ Perfect for status indicators, banners, and UI components

Basic Usage

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

function DraftBanner() {
const { hasDraft, updatedAt, isExpired, isChecking } = useDraftStatus('my-form');

if (isChecking) {
return <div>Checking for draft...</div>;
}

if (!hasDraft) {
return null;
}

if (isExpired) {
return <div>Your draft has expired</div>;
}

return (
<div>
Draft saved at {new Date(updatedAt!).toLocaleString()}
</div>
);
}

API Reference

Parameters

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

Options

interface UseDraftStatusOptions {
includeOrigin?: boolean; // Include origin in draft ID (default: true)
storagePrefix?: string; // Storage key prefix (default: 'fg')
ttl?: number | { // Time to live for drafts
days?: number;
hours?: number;
minutes?: number;
};
}

Return Value

interface UseDraftStatusResult {
hasDraft: boolean; // Whether draft exists
isExpired: boolean; // Whether draft has expired
updatedAt: number | null; // Timestamp of draft (if exists)
isChecking: boolean; // Whether status is being checked
refresh: () => Promise<void>; // Manually refresh status
clear: () => Promise<void>; // Clear draft from storage
}

Examples

Simple Status Indicator

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

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

if (!hasDraft) return null;

return (
<div className="draft-indicator">
💾 Draft saved {new Date(updatedAt!).toLocaleString()}
</div>
);
}

Status with Expiration

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

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

if (!hasDraft) {
return <div>No draft available</div>;
}

if (isExpired) {
return <div className="expired">⚠️ Draft has expired</div>;
}

return (
<div className="active">
✓ Draft saved {new Date(updatedAt!).toLocaleString()}
</div>
);
}

Manual Refresh

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

function DraftStatusWithRefresh() {
const { hasDraft, updatedAt, isChecking, refresh } = useDraftStatus('my-form');

return (
<div>
{isChecking ? (
<span>Checking...</span>
) : hasDraft ? (
<span>Draft: {new Date(updatedAt!).toLocaleString()}</span>
) : (
<span>No draft</span>
)}
<button onClick={() => refresh()}>Refresh</button>
</div>
);
}

Clear Draft

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

function DraftControls() {
const { hasDraft, updatedAt, clear } = useDraftStatus('my-form');

const handleClear = async () => {
await clear();
// Draft is now cleared
// hasDraft will be false on next render
};

return (
<div>
{hasDraft && (
<div>
<span>Draft: {new Date(updatedAt!).toLocaleString()}</span>
<button onClick={handleClear}>Clear Draft</button>
</div>
)}
</div>
);
}

Use Cases

1. Draft Banner

Show a banner when a draft exists:

function DraftBanner() {
const { hasDraft, updatedAt } = useDraftStatus('checkout-form');

if (!hasDraft) return null;

return (
<div className="banner">
You have an unsaved draft from {new Date(updatedAt!).toLocaleString()}
</div>
);
}

2. Navigation Indicator

Show draft status in navigation:

function Navigation() {
const { hasDraft } = useDraftStatus('contact-form');

return (
<nav>
<Link href="/contact">Contact {hasDraft && '💾'}</Link>
</nav>
);
}

3. Form Header Status

Show status in form header:

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

return (
<header>
<h1>My Form</h1>
{hasDraft && (
<div className="status">
{isExpired ? (
<span className="expired">Draft expired</span>
) : (
<span>Last saved: {new Date(updatedAt!).toLocaleString()}</span>
)}
</div>
)}
</header>
);
}

4. Multiple Forms

Track status for multiple forms:

function MultiFormStatus() {
const contactStatus = useDraftStatus('contact-form');
const checkoutStatus = useDraftStatus('checkout-form');

return (
<div>
<div>Contact: {contactStatus.hasDraft ? '💾' : '✗'}</div>
<div>Checkout: {checkoutStatus.hasDraft ? '💾' : '✗'}</div>
</div>
);
}

Best Practices

1. Match Configuration

When using with useFormAutosave, match the configuration:

// useFormAutosave
const { formRef } = useFormAutosave('my-form', {
includeOrigin: true,
storagePrefix: 'fg',
ttl: { days: 7 },
});

// useDraftStatus (must match)
const { hasDraft } = useDraftStatus('my-form', {
includeOrigin: true, // Must match
storagePrefix: 'fg', // Must match
ttl: { days: 7 }, // Must match
});

2. Handle Loading State

const { hasDraft, isChecking } = useDraftStatus('my-form');

if (isChecking) {
return <Skeleton />; // Show loading state
}

// Render actual content

Performance

  • Lightweight - No DOM manipulation, only storage reads
  • Efficient - Only reads when needed
  • Cached - Status is cached until refresh or broadcast update
  • SSR Safe - Returns safe defaults on server

Next Steps