Autosave WYSIWYG Editors
WYSIWYG editors like TipTap and Quill require special handling for autosave. This guide shows you how to integrate them with Form Guardian.
TipTap Editor
TipTap is a modern WYSIWYG editor. Here's how to integrate it:
import { useEditor, EditorContent } from '@tiptap/react';
import StarterKit from '@tiptap/starter-kit';
import { useFormAutosave } from '@form-guardian/react';
import { useEffect, useCallback } from 'react';
function TipTapForm() {
const { formRef, restoreValues, clearDraft, saveValues } = useFormAutosave(
'tiptap-form',
{ autoRestore: false }
);
const editor = useEditor({
extensions: [StarterKit],
content: '',
onUpdate: ({ editor }) => {
// Save on every update (debounced by Form Guardian)
const content = editor.getHTML();
// Form Guardian will automatically save this
},
});
// Restore draft on mount
useEffect(() => {
if (editor) {
restoreValues(
(field, value) => {
if (field === 'content' && typeof value === 'string') {
editor.commands.setContent(value);
}
},
() => ({
content: editor.getHTML(),
})
);
}
}, [editor]);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
const content = editor?.getHTML() || '';
// Submit logic
await clearDraft();
};
return (
<form ref={formRef} onSubmit={handleSubmit}>
<EditorContent editor={editor} />
<button type="submit">Submit</button>
</form>
);
}
Quill Editor
Quill is another popular WYSIWYG editor:
import { useQuill } from 'react-quill-new';
import 'react-quill-new/dist/quill.snow.css';
import { useFormAutosave } from '@form-guardian/react';
import { useEffect, useRef } from 'react';
function QuillForm() {
const quillRef = useRef<any>(null);
const { formRef, restoreValues, clearDraft } = useFormAutosave(
'quill-form',
{ autoRestore: false }
);
const { quill, quillRef: quillElementRef } = useQuill({
theme: 'snow',
});
useEffect(() => {
if (quill) {
quillRef.current = quill;
// Restore draft
restoreValues(
(field, value) => {
if (field === 'content' && typeof value === 'string') {
quill.root.innerHTML = value;
}
},
() => ({
content: quill.root.innerHTML,
})
);
// Save on text change
quill.on('text-change', () => {
// Form Guardian will automatically save
});
}
}, [quill]);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
const content = quill?.root.innerHTML || '';
// Submit logic
await clearDraft();
};
return (
<form ref={formRef} onSubmit={handleSubmit}>
<div ref={quillElementRef} />
<button type="submit">Submit</button>
</form>
);
}
ContentEditable Element
For simple contentEditable elements:
import { useFormAutosave } from '@form-guardian/react';
import { useRef, useEffect } from 'react';
function ContentEditableForm() {
const editorRef = useRef<HTMLDivElement>(null);
const { formRef, restoreValues, clearDraft } = useFormAutosave(
'contenteditable-form',
{ autoRestore: false }
);
useEffect(() => {
if (editorRef.current) {
restoreValues(
(field, value) => {
if (field === 'content' && editorRef.current) {
editorRef.current.innerHTML = value as string;
}
},
() => ({
content: editorRef.current?.innerHTML || '',
})
);
}
}, []);
return (
<form ref={formRef}>
<div
ref={editorRef}
contentEditable
data-track="true"
style={{ border: '1px solid #ccc', minHeight: '200px', padding: '10px' }}
/>
<button type="submit">Submit</button>
</form>
);
}
With React Hook Form
Integrating with React Hook Form:
import { useForm } from 'react-hook-form';
import { useEditor, EditorContent } from '@tiptap/react';
import StarterKit from '@tiptap/starter-kit';
import { useFormAutosave } from '@form-guardian/react';
import { useEffect } from 'react';
interface FormData {
title: string;
content: string;
}
function TipTapWithRHF() {
const { register, handleSubmit, setValue, getValues, watch } = useForm<FormData>({
defaultValues: {
title: '',
content: '',
},
});
const editor = useEditor({
extensions: [StarterKit],
content: '',
onUpdate: ({ editor }) => {
setValue('content', editor.getHTML(), { shouldDirty: true });
},
});
const { formRef, restoreValues, clearDraft } = useFormAutosave(
'tiptap-rhf-form',
{ autoRestore: false }
);
useEffect(() => {
if (editor) {
restoreValues(
(field, value) => {
if (field === 'content' && typeof value === 'string') {
editor.commands.setContent(value);
setValue('content', value, { shouldValidate: false });
} else {
setValue(field as keyof FormData, value, { shouldValidate: false });
}
},
() => getValues()
);
}
}, [editor]);
const onSubmit = async (data: FormData) => {
await submitForm(data);
await clearDraft();
};
return (
<form ref={formRef} onSubmit={handleSubmit(onSubmit)}>
<input {...register('title', { required: true })} placeholder="Title" />
<EditorContent editor={editor} />
<button type="submit">Submit</button>
</form>
);
}
Handling Large Content
For large content, consider optimizing:
function OptimizedTipTapForm() {
const { formRef, restoreValues, clearDraft } = useFormAutosave(
'tiptap-form',
{
autoRestore: false,
debounceMs: 1000, // Longer debounce for large content
}
);
const editor = useEditor({
extensions: [StarterKit],
content: '',
onUpdate: ({ editor }) => {
// Only save HTML, not full editor state
const html = editor.getHTML();
// Form Guardian handles the rest
},
});
// Restore only HTML content, not full editor state
useEffect(() => {
if (editor) {
restoreValues(
(field, value) => {
if (field === 'content' && typeof value === 'string') {
editor.commands.setContent(value, false); // false = don't add to history
}
},
() => ({
content: editor.getHTML(),
})
);
}
}, [editor]);
return (
<form ref={formRef}>
<EditorContent editor={editor} />
</form>
);
}
Best Practices
- Save HTML, not editor state - Editor state can be large and editor-specific
- Use longer debounce - WYSIWYG content changes frequently
- Handle restoration carefully - Set content without adding to undo history
- Clear drafts after submission - Prevent stale content
Common Pitfalls
❌ Don't Save Full Editor State
// ❌ Wrong - editor state is large and editor-specific
const state = editor.getJSON();
saveDraft({ state });
✅ Save HTML Content
// ✅ Correct - HTML is portable and smaller
const html = editor.getHTML();
saveDraft({ content: html });
Next Steps
- 📖 Getting Started - Basic setup
- 🍳 File Uploads - Handle file uploads
- 📚 API Reference - Complete API documentation