{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "controlled-docs",
  "title": "Controlled Editor Value",
  "description": "Control initial values, persistence, replacement, and async initialization.",
  "files": [
    {
      "path": "../../content/docs/(guides)/controlled.mdx",
      "content": "---\ntitle: Controlled Editor Value\ndescription: Control initial values, persistence, replacement, and async initialization.\n---\n\nPlate is not a normal controlled text input. The editor owns content, selection, history, plugin state, and normalization. This guide shows the safe control points: initial values, change persistence, explicit replacement, reset, and delayed initialization.\n\n## Value Ownership\n\n<Callout type=\"warning\" title=\"Do not control every keystroke\">\n  Do not mirror `editor.children` into React state and pass it back on every\n  change. That fights Slate selection/history and turns normal typing into a\n  full-document replacement loop.\n</Callout>\n\n| Goal | API |\n| --- | --- |\n| Set initial content. | `value` in `usePlateEditor` or `createPlateEditor`. |\n| Persist edits. | `<Plate onValueChange>` or `<Plate onChange>`. |\n| Replace content from outside the editor. | `editor.tf.setValue(value)`. |\n| Reset editor state. | `editor.tf.reset()`. |\n| Delay initialization. | `skipInitialization: true` plus `editor.tf.init(...)`. |\n\n<Steps>\n\n### Set the Initial Value\n\nPass a `Value`, an HTML string, a function, or an async function to `value`.\n\n```tsx title=\"components/editor.tsx\" showLineNumbers\nimport type { Value } from 'platejs';\nimport { Plate, usePlateEditor } from 'platejs/react';\n\nimport { Editor, EditorContainer } from '@/components/ui/editor';\n\nconst initialValue: Value = [\n  {\n    children: [{ text: 'Initial value' }],\n    type: 'p',\n  },\n];\n\nexport function MyEditor() {\n  const editor = usePlateEditor({\n    value: initialValue,\n  });\n\n  return (\n    <Plate editor={editor}>\n      <EditorContainer>\n        <Editor />\n      </EditorContainer>\n    </Plate>\n  );\n}\n```\n\n### Persist Changes\n\nUse `onValueChange` when you only need the document value.\n\n```tsx title=\"components/editor.tsx\" showLineNumbers {15-19,25}\nimport type { Value } from 'platejs';\nimport { Plate, usePlateEditor } from 'platejs/react';\n\nimport { Editor, EditorContainer } from '@/components/ui/editor';\n\nconst STORAGE_KEY = 'plate-value';\n\nconst initialValue: Value = [\n  {\n    children: [{ text: 'Autosaved value' }],\n    type: 'p',\n  },\n];\n\nfunction saveValue(value: Value) {\n  localStorage.setItem(STORAGE_KEY, JSON.stringify(value));\n}\n\nexport function MyEditor() {\n  const editor = usePlateEditor({\n    value: () => {\n      const saved = localStorage.getItem(STORAGE_KEY);\n\n      return saved ? JSON.parse(saved) : initialValue;\n    },\n  });\n\n  return (\n    <Plate editor={editor} onValueChange={({ value }) => saveValue(value)}>\n      <EditorContainer>\n        <Editor />\n      </EditorContainer>\n    </Plate>\n  );\n}\n```\n\nUse `onChange` when the callback needs the editor instance too.\n\n```tsx title=\"components/editor.tsx\"\n<Plate\n  editor={editor}\n  onChange={({ editor, value }) => {\n    console.info(editor.id, value);\n  }}\n/>\n```\n\n### Replace or Reset Content\n\nUse transforms for external changes. `setValue` replaces the document and\n`reset` returns the editor to its initialized state.\n\n```tsx title=\"components/replace-controls.tsx\" showLineNumbers\nimport type { Value } from 'platejs';\nimport { useEditorRef } from 'platejs/react';\n\nimport { Button } from '@/components/ui/button';\n\nconst replacementValue: Value = [\n  {\n    children: [{ text: 'Replaced value' }],\n    type: 'p',\n  },\n];\n\nexport function ReplaceControls() {\n  const editor = useEditorRef();\n\n  return (\n    <div className=\"flex gap-2\">\n      <Button onClick={() => editor.tf.setValue(replacementValue)}>\n        Replace Value\n      </Button>\n      <Button onClick={() => editor.tf.reset()}>Reset Editor</Button>\n    </div>\n  );\n}\n```\n\n<Callout type=\"info\">\n  `editor.tf.setValue` replaces nodes at the document root. Use it for explicit\n  outside-editor changes, not for every `onValueChange`.\n</Callout>\n\n<ComponentPreview name=\"controlled-demo\" padding=\"md\" />\n\n### Load Async Initial Content\n\nUse an async `value` function when the editor can initialize as soon as the data\nresolves.\n\n```tsx title=\"components/async-editor.tsx\" showLineNumbers\nimport { Plate, usePlateEditor } from 'platejs/react';\n\nimport { Editor, EditorContainer } from '@/components/ui/editor';\n\nexport function AsyncEditor() {\n  const editor = usePlateEditor({\n    autoSelect: 'end',\n    value: async () => {\n      const response = await fetch('/api/document');\n      const data = await response.json();\n\n      return data.content;\n    },\n    onReady: ({ isAsync, value }) => {\n      if (isAsync) console.info('Loaded value:', value);\n    },\n  });\n\n  return (\n    <Plate editor={editor}>\n      <EditorContainer>\n        <Editor />\n      </EditorContainer>\n    </Plate>\n  );\n}\n```\n\n### Initialize Manually\n\nUse `skipInitialization` when another system owns the startup moment, such as\ncollaboration or a multi-step loader.\n\n```tsx title=\"components/manual-init-editor.tsx\" showLineNumbers {8,13-18}\nimport * as React from 'react';\nimport { Plate, usePlateEditor } from 'platejs/react';\n\nimport { Editor, EditorContainer } from '@/components/ui/editor';\n\nexport function ManualInitEditor() {\n  const editor = usePlateEditor({\n    skipInitialization: true,\n  });\n\n  React.useEffect(() => {\n    void fetch('/api/document')\n      .then((response) => response.json())\n      .then((data) => {\n        editor.tf.init({\n          autoSelect: 'end',\n          value: data.content,\n        });\n      });\n  }, [editor]);\n\n  return (\n    <Plate editor={editor}>\n      <EditorContainer>\n        <Editor />\n      </EditorContainer>\n    </Plate>\n  );\n}\n```\n\n</Steps>\n\nDone. Plate owns live editor state; your app controls the entry points around it.\n",
      "type": "registry:file",
      "target": "content/docs/plate/(guides)/controlled.mdx"
    }
  ],
  "type": "registry:file"
}