# React integration

Mount the editor and portals through the React adapter while preserving application context.

## Minimal integration

`MsfeEditorContent` handles mounting and cleanup, and `onReady` exposes the current instance. Options provide initial configuration; use instance methods to update an editor that is already mounted.

```tsx
import { MsfeEditorContent } from '@quirekit/msfe-react'
import { StarterKit } from '@quirekit/msfe-starter-kit'
import type { ReactEditorOptions } from '@quirekit/msfe-react'

const options: ReactEditorOptions = {
  markdown: '# Hello, Markdown',
  mode: 'live',
  extensions: [StarterKit],
}

/** @returns The editor component in the current application. */
export function EditorExample() {
  return <MsfeEditorContent options={options} />
}
```

## Custom host layout

To place the mount element yourself, use `useMsfeEditor(parentRef, optionsFactory)`. It returns the current session or `null`; the session contains `editor` and `host`. Keep `ReactNodeViewHost` within your existing provider tree.

```tsx
import { useRef } from 'react'
import { useMsfeEditor, ReactNodeViewHost } from '@quirekit/msfe-react'
import { StarterKit } from '@quirekit/msfe-starter-kit'

/** @returns An editor and component-view host that retain context. */
export function EditorWithHost() {
  const parent = useRef<HTMLDivElement>(null)
  const session = useMsfeEditor(parent, () => ({
    markdown: 'Start editing.',
    extensions: [StarterKit],
  }))

  return <>
    <div ref={parent} />
    {session && <ReactNodeViewHost host={session.host} />}
  </>
}
```

## Context and StrictMode

`ReactNodeViewRenderer()` and `ReactComponentViewRenderer()` use portals within the same host. Development StrictMode repeats the effect lifecycle, so `onReady` may run again; do not treat it as a business action that runs exactly once.

QuireKit's public component library currently provides Vue components. The React adapter supports your application's React UI while keeping the field and transaction contracts consistent.

## React NodeView shape

The component receives nodeView and reuses the framework-independent codec. Use the Portal Host to preserve Context instead of creating a separate React root per node.

~~~tsx
import type { MsfeNodeViewProps } from '@quirekit/msfe-core'
import { MsfeEditorContent, ReactNodeViewRenderer } from '@quirekit/msfe-react'
import { StarterKit } from '@quirekit/msfe-starter-kit'
import { Status } from './status'

interface StatusAttributes { readonly value: 'todo' | 'done' }
function StatusView({ nodeView }: { nodeView: MsfeNodeViewProps<StatusAttributes> }) {
  return <button disabled={nodeView.readOnly} onClick={() => nodeView.updateAttributes({
    value: nodeView.attrs.value === 'done' ? 'todo' : 'done',
  })}>{nodeView.attrs.value}</button>
}
// Status is the framework-independent extension from the custom-node tutorial.
const StatusWithUI = Status.extend({
  addNodeView() { return ReactNodeViewRenderer(StatusView) },
})

/** @returns A mounted editor using the custom React node view. */
export function StatusEditor() {
  return <MsfeEditorContent options={{
    markdown: '# Status\n\n[[todo]]', mode: 'live',
    extensions: [StarterKit, StatusWithUI],
  }} />
}
~~~

Copy the framework-independent `status.ts` and `types.ts` from the [node tutorial](/en/extensions/custom-node) beside this component first. Replace the illustrative native button with your React component library. Generic controls use ReactComponentViewRenderer and receive view, not nodeView.

## Updates and cleanup

Saving, modes and external changes use the same instance APIs. Avoid feeding whole-document changes back into options and triggering remounts. Re-establish host subscriptions for each session and clean up effects rather than retaining stale editor references.

React snippets describe the public integration. This site's interactive examples run in Vue; consuming React apps should verify StrictMode, SSR and routing themselves.
