WYSIWYG редакторы
Данная страница находится в процессе перевода. Временно доступна английская версия.
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