useDrafts
Location: src/hooks/useDrafts.js
Overview
Manages composer drafts stored in the composer_drafts Supabase table. Supports multiple editor types (simple, longform, drawing, magnetic_poetry), auto-saving with debounce, pinning, renaming, duplicating, and two loading modes: "content only" (mix-and-match) and "full" (content + intent + style).
Use this hook in the post composer to persist work-in-progress posts and provide a drafts management interface.
Signature
function useDrafts(userId: string): {
// State
drafts: Draft[],
loading: boolean,
currentDraft: Draft | null,
hasUnsavedChanges: boolean,
// CRUD
loadDrafts: () => Promise<void>,
saveDraft: (options: SaveDraftOptions) => Promise<Draft | null>,
loadDraftContent: (draftId: string) => { content, editorType } | null,
loadDraftFull: (draftId: string) => { content, intent, postStyle, editorType } | null,
deleteDraft: (draftId: string) => Promise<boolean>,
pinDraft: (draftId: string, isPinned: boolean) => Promise<boolean>,
renameDraft: (draftId: string, newTitle: string) => Promise<boolean>,
duplicateDraft: (draftId: string) => Promise<Draft | null>,
// Auto-save
autoSave: (options: AutoSaveOptions) => void,
clearAutoSave: () => Promise<void>,
// State management
markUnsaved: () => void,
startNew: () => void
}
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
userId | string | Yes | The current user's ID. Drafts are scoped to this user. |
Return Value
| Property | Type | Description |
|---|---|---|
drafts | Draft[] | All drafts for the user, sorted by pinned status then last edited |
loading | boolean | Whether drafts are being loaded |
currentDraft | Draft | null | The draft currently being edited |
hasUnsavedChanges | boolean | Whether the current draft has unsaved changes |
loadDrafts | () => Promise<void> | Manually reload all drafts |
saveDraft | (options) => Promise<Draft | null> | Save a new draft or update an existing one. Options: id, content, intent, postStyle, title, editorType, isAutoSave |
loadDraftContent | (draftId) => object | null | Load only content and editor type from a draft (no intent -- for mix-and-match) |
loadDraftFull | (draftId) => object | null | Load full draft: content, intent, post style, and editor type |
deleteDraft | (draftId) => Promise<boolean> | Delete a draft. Returns true on success. |
pinDraft | (draftId, isPinned) => Promise<boolean> | Pin or unpin a draft |
renameDraft | (draftId, newTitle) => Promise<boolean> | Rename a draft |
duplicateDraft | (draftId) => Promise<Draft | null> | Duplicate a draft with "(copy)" appended to the title |
autoSave | (options) => void | Debounced auto-save (3-second delay). Params: content, intent, postStyle, editorType |
clearAutoSave | () => Promise<void> | Delete the auto-save draft (call after posting) |
markUnsaved | () => void | Mark the current draft as having unsaved changes |
startNew | () => void | Clear the current draft and start fresh |
Usage
import { useDrafts } from '../hooks/useDrafts';
function Composer({ userId }) {
const {
drafts,
currentDraft,
saveDraft,
autoSave,
clearAutoSave,
loadDraftFull,
startNew
} = useDrafts(userId);
const handleContentChange = (content, intent, postStyle) => {
autoSave({ content, intent, postStyle, editorType: 'simple' });
};
const handlePost = async () => {
// ... submit post logic
await clearAutoSave();
startNew();
};
return (
<div>
<DraftsList
drafts={drafts}
onSelect={(id) => loadDraftFull(id)}
/>
<Editor onChange={handleContentChange} />
</div>
);
}
Notes
- Auto-save uses a 3-second debounce via
lodash/debounce. - Drafts are sorted with pinned items first, then by
last_edited_atdescending. - Preview text is auto-generated from content (max 100 characters).
- Drawing drafts display
[Drawing]as their preview.
Last updated: 2026-02-07