{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "form-docs",
  "title": "Form",
  "description": "How to integrate Plate editor with react-hook-form.",
  "files": [
    {
      "path": "../../content/docs/(guides)/form.mdx",
      "content": "---\ntitle: Form\ndescription: How to integrate Plate editor with react-hook-form.\n---\n\nWhile Plate is typically used as an **uncontrolled** input, there are valid scenarios where you want to integrate the editor within a form library like [**react-hook-form**](https://www.react-hook-form.com) or the [**Form**](https://ui.shadcn.com/docs/components/form) component from **shadcn/ui**. This guide walks through best practices and common pitfalls.\n\n## When to Integrate Plate with a Form\n\n- **Form Submission**: You want the editor's content to be included along with other fields (e.g., `<input>`, `<select>`) when the user submits the form.\n- **Validation**: You want to validate the editor's content (e.g., checking if it's empty) at the same time as other form fields.\n- **Form Data Management**: You want to store the editor content in the same store (like `react-hook-form`'s state) as other fields.\n\nHowever, keep in mind the warning about **fully controlling** the editor value. Plate strongly prefer an uncontrolled model. If you attempt to replace the editor's internal state too frequently, you can break **selection**, **history**, or cause performance issues. The recommended pattern is to treat the editor as uncontrolled, but still **sync** form data on certain events.\n\n## Approach 1: Sync on `onChange`\n\nThis is the most straightforward approach: each time the editor changes, update your form field's value. For small documents or infrequent changes, this is usually acceptable.\n\n### React Hook Form Example\n\n```tsx\nimport { useForm } from 'react-hook-form';\nimport type { Value } from 'platejs';\nimport { Plate, PlateContent, usePlateEditor } from 'platejs/react';\n\ntype FormData = {\n  content: Value;\n};\n\nexport function RHFEditorForm() {\n  const initialValue = [\n    { type: 'p', children: [{ text: 'Hello from react-hook-form!' }] },\n  ]\n\n  // Setup react-hook-form\n  const { register, handleSubmit, setValue } = useForm<FormData>({\n    defaultValues: {\n      content: initialValue,\n    },\n  });\n\n  // Create/configure the Plate editor\n  const editor = usePlateEditor({ value: initialValue });\n\n  // Register the field for react-hook-form\n  register('content', { /* validation rules... */ });\n\n  const onSubmit = (data: FormData) => {\n    // data.content will have final editor content\n    console.info('Submitted:', data.content);\n  };\n\n  return (\n    <form onSubmit={handleSubmit(onSubmit)}>\n      <Plate\n        editor={editor}\n        onChange={({ value }) => {\n          // Sync editor changes to the form\n          setValue('content', value);\n        }}\n      >\n        <PlateContent placeholder=\"Type here...\" />\n      </Plate>\n\n      <button type=\"submit\">Submit</button>\n    </form>\n  );\n}\n```\n\n**Notes**:\n1. **`defaultValues.content`**: your initial editor content.\n2. **`register('content')`**: signals to RHF that the field is tracked.  \n3. **`onChange({ value })`**: calls `setValue('content', value)` each time.\n\nIf you expect large documents or fast typing, consider debouncing or switching to an `onBlur` approach to reduce form updates.\n\n### shadcn/ui Form Example\n\n[shadcn/ui](https://ui.shadcn.com/docs/components/form) provides a `<Form>` that integrates with react-hook-form. We'll use `<FormField>` to handle the field logic:\n\n```tsx\nimport {\n  Form,\n  FormControl,\n  FormField,\n  FormItem,\n  FormLabel,\n  FormMessage,\n} from '@/components/ui/form';\nimport { useForm } from 'react-hook-form';\nimport { Plate, PlateContent, usePlateEditor } from 'platejs/react';\n\ntype FormValues = {\n  content: any;\n};\n\nexport function EditorForm() {\n  // 1. Create the form\n  const form = useForm<FormValues>({\n    defaultValues: {\n      content: [\n        { type: 'p', children: [{ text: 'Hello from shadcn/ui Form!' }] },\n      ],\n    },\n  });\n\n  // 2. Create the Plate editor\n  const editor = usePlateEditor();\n\n  const onSubmit = (data: FormValues) => {\n    console.info('Submitted data:', data.content);\n  };\n\n  return (\n    <Form {...form}>\n      <form onSubmit={form.handleSubmit(onSubmit)}>\n        <FormField\n          control={form.control}\n          name=\"content\"\n          render={({ field }) => (\n            <FormItem>\n              <FormLabel>Content</FormLabel>\n              <FormControl>\n                <Plate\n                  editor={editor}\n                  onChange={({ value }) => {\n                    // Sync to the form\n                    field.onChange(value);\n                  }}\n                >\n                  <PlateContent placeholder=\"Type...\" />\n                </Plate>\n              </FormControl>\n              <FormMessage />\n            </FormItem>\n          )}\n        />\n\n        <button type=\"submit\">Submit</button>\n      </form>\n    </Form>\n  );\n}\n```\n\nThis approach makes your editor content behave like any other field in the shadcn/ui form.\n\n## Approach 2: Sync on Blur (or Another Trigger)\n\nInstead of syncing on every keystroke, you might only need the final value when the user:\n- Leaves the editor (`onBlur`),\n- Clicks a “Save” button, or\n- Reaches certain form submission logic.\n\n```tsx\n<Plate editor={editor}>\n  <PlateContent\n    onBlur={() => {\n      // Only sync on blur\n      setValue('content', editor.children);\n    }}\n  />\n</Plate>\n```\n\nThis reduces overhead but your form state won't reflect partial updates while the user is typing.\n\n## Approach 3: Controlled Replacement (Advanced)\n\nIf you want the form to be the single source of truth (completely [controlled](/docs/controlled)):\n```ts\neditor.tf.setValue(formStateValue);\n```\nBut this has known drawbacks:\n- Potentially breaks cursor position and undo/redo.\n- Can cause frequent full re-renders for large docs.\n\n**Recommendation**: Stick to a partially uncontrolled model if you can.\n\n## Example: Save & Reset\n\nHere's a more complete form demonstrating both saving and resetting the editor/form:\n\n```tsx\nimport { useForm } from 'react-hook-form';\nimport { Plate, PlateContent, usePlateEditor } from 'platejs/react';\n\nfunction MyForm() {\n  const form = useForm({\n    defaultValues: {\n      content: [\n        { type: 'p', children: [{ text: 'Initial content...' }] },\n      ],\n    },\n  });\n\n  const editor = usePlateEditor();\n\n  const onSubmit = (data) => {\n    alert(JSON.stringify(data, null, 2));\n  };\n\n  return (\n    <form onSubmit={form.handleSubmit(onSubmit)}>\n      <Plate\n        editor={editor}\n        onChange={({ value }) => form.setValue('content', value)}\n      >\n        <PlateContent />\n      </Plate>\n\n      <button type=\"submit\">Save</button>\n\n      <button\n        type=\"button\"\n        onClick={() => {\n          // Reset the editor\n          editor.tf.reset();\n          // Reset the form\n          form.reset();\n        }}\n      >\n        Reset\n      </button>\n    </form>\n  );\n}\n```\n\n- **`onChange`** -> updates form state.\n- **Reset** -> calls both `editor.tf.reset()` and `form.reset()` for consistency.\n\n## Migrating from a shadcn Textarea to Plate\n\nIf you have a standard [TextareaForm from shadcn/ui docs](https://ui.shadcn.com/docs/components/textarea#form) and want to replace `<Textarea>` with a Plate editor, you can follow these steps:\n\n```tsx\n// 1. Original code (TextareaForm)\n<FormField\n  control={form.control}\n  name=\"bio\"\n  render={({ field }) => (\n    <FormItem>\n      <FormLabel>Bio</FormLabel>\n      <FormControl>\n        <Textarea\n          placeholder=\"Tell us a bit about yourself\"\n          className=\"resize-none\"\n          {...field}\n        />\n      </FormControl>\n      <FormDescription>\n        You can <span>@mention</span> other users and organizations.\n      </FormDescription>\n      <FormMessage />\n    </FormItem>\n  )}\n/>\n```\n\nCreate a new `EditorField` component:\n\n```tsx\n// EditorField.tsx\n\"use client\";\n\nimport * as React from \"react\";\nimport type { Value } from \"platejs\";\nimport { Plate, PlateContent, usePlateEditor } from \"platejs/react\";\n\n/**\n * A reusable editor component that works like a standard <Textarea>,\n * accepting `value`, `onChange`, and optional placeholder.\n *\n * Usage:\n *\n * <FormField\n *   control={form.control}\n *   name=\"bio\"\n *   render={({ field }) => (\n *     <FormItem>\n *       <FormLabel>Bio</FormLabel>\n *       <FormControl>\n *         <EditorField\n *           {...field}\n *           placeholder=\"Tell us a bit about yourself\"\n *         />\n *       </FormControl>\n *       <FormDescription>Some helpful description...</FormDescription>\n *       <FormMessage />\n *     </FormItem>\n *   )}\n * />\n */\nexport interface EditorFieldProps\n  extends React.HTMLAttributes<HTMLDivElement> {\n  /**\n   * The current Plate Value. Should be an array of Plate nodes.\n   */\n  value?: Value;\n\n  /**\n   * Called when the editor value changes.\n   */\n  onChange?: (value: Value) => void;\n\n  /**\n   * Placeholder text to display when editor is empty.\n   */\n  placeholder?: string;\n}\n\nexport function EditorField({\n  value,\n  onChange,\n  placeholder = \"Type here...\",\n  ...props\n}: EditorFieldProps) {\n  // We create our editor instance with the provided initial `value`.\n  const editor = usePlateEditor({\n    value: value ?? [\n      { type: \"p\", children: [{ text: \"\" }] }, // Default empty paragraph\n    ],\n  });\n\n  return (\n    <Plate\n      editor={editor}\n      onChange={({ value }) => {\n        // Sync changes back to the caller via onChange prop\n        onChange?.(value);\n      }}\n      {...props}\n    >\n      <PlateContent placeholder={placeholder} />\n    </Plate>\n  );\n}\n```\n\n3. Replace the `<Textarea>` with a `<EditorField>` block:\n\n```tsx\n\"use client\";\n\nimport { z } from \"zod\";\nimport { useForm } from \"react-hook-form\";\nimport { zodResolver } from \"@hookform/resolvers/zod\";\n\nimport {\n  Form,\n  FormControl,\n  FormDescription,\n  FormField,\n  FormItem,\n  FormLabel,\n  FormMessage,\n} from \"@/components/ui/form\";\nimport { EditorField } from \"./EditorField\"; // Import the component above\n\n// 1. Define our validation schema with zod\nconst FormSchema = z.object({\n  bio: z\n    .string()\n    .min(10, { message: \"Bio must be at least 10 characters.\" })\n    .max(160, { message: \"Bio must not exceed 160 characters.\" }),\n});\n\n// 2. Build our main form component\nexport function EditorForm() {\n  // 3. Setup the form\n  const form = useForm<z.infer<typeof FormSchema>>({\n    resolver: zodResolver(FormSchema),\n    defaultValues: {\n      // Here \"bio\" is just a string, but our editor\n      // will interpret it as initial content if you parse it as JSON\n      bio: \"\",\n    },\n  });\n\n  // 4. Submission handler\n  function onSubmit(data: z.infer<typeof FormSchema>) {\n    alert(\"Submitted: \" + JSON.stringify(data, null, 2));\n  }\n\n  return (\n    <Form {...form}>\n      <form onSubmit={form.handleSubmit(onSubmit)} className=\"space-y-8\">\n        <FormField\n          control={form.control}\n          name=\"bio\"\n          render={({ field }) => (\n            <FormItem>\n              <FormLabel>Bio</FormLabel>\n              <FormControl>\n                <EditorField\n                  {...field}\n                  placeholder=\"Tell us a bit about yourself...\"\n                />\n              </FormControl>\n              <FormDescription>\n                You can <span>@mention</span> other users and organizations.\n              </FormDescription>\n              <FormMessage />\n            </FormItem>\n          )}\n        />\n        <button type=\"submit\" className=\"py-2 px-4 bg-primary text-white\">\n          Submit\n        </button>\n      </form>\n    </Form>\n  );\n}\n```\n\n- Any existing form validations or error messages remain the same.\n- For default values, ensure `form.defaultValues.bio` is a valid Plate value (array of nodes) instead of a string.\n- For controlled values, use `editor.tf.setValue(formStateValue)` with moderation.\n\n## Best Practices\n\n1. **Use an Uncontrolled Editor**: Let Plate manage its own state, updating the form only when necessary.  \n2. **Minimize Replacements**: Avoid calling `editor.tf.setValue` too often; it can break selection, history, or degrade performance.  \n3. **Validate at the Right Time**: Decide if you need instant validation (typing) or upon blur/submit.  \n4. **Reset Both**: If you reset the form, call `editor.tf.reset()` to keep them in sync.\n",
      "type": "registry:file",
      "target": "content/docs/plate/(guides)/form.mdx"
    }
  ],
  "type": "registry:file"
}