Skip to content

Getting Started

Jayden Smith edited this page Apr 12, 2026 · 3 revisions

What This Package Is

@apollohg/react-native-prose-editor is a native rich text editor for React Native.

It does not render inside a WebView. Instead:

  • React Native hosts the public component API
  • iOS and Android handle rendering and input natively
  • the Rust core owns document structure, transforms, schema validation, and history

Before You Start

If you have not installed the repo dependencies and example app yet, start with the Installation Guide.

This package currently requires Expo Modules, and the minimum tested Expo version is SDK 54.

First Editor

import {
  NativeRichTextEditor,
  DEFAULT_EDITOR_TOOLBAR_ITEMS,
  tiptapSchema,
} from '@apollohg/react-native-prose-editor';

export function Example() {
  return (
    <NativeRichTextEditor
      initialContent="<p>Hello world</p>"
      placeholder="Write something..."
      schema={tiptapSchema}
      toolbarItems={DEFAULT_EDITOR_TOOLBAR_ITEMS}
      showToolbar
    />
  );
}

Content Modes

The editor supports both uncontrolled and controlled usage.

Content Priority

Initialization happens in this order:

Priority Prop
1 value
2 valueJSON
3 initialJSON
4 initialContent

Uncontrolled Mode

Use uncontrolled mode when the editor owns its own working document state.

  • initialContent
  • initialJSON

Controlled Mode

Use controlled mode when your app owns the current document.

  • value
  • valueJSON

If both value and valueJSON are provided, value wins.

For whole-document JSON loads, an empty root doc like { type: 'doc', content: [] } is normalized to a schema-valid empty text block for the active schema. That applies to initialJSON, controlled valueJSON, and imperative whole-document replacement APIs such as setContentJson(). Fragment insertion APIs such as insertContentJson() still use the content you pass through unchanged.

Common Setup Patterns

These snippets are component fragments. Import the hooks and types they use in your screen or component.

Simple Built-In Toolbar

<NativeRichTextEditor
  initialContent="<p>Hello world</p>"
  showToolbar
  toolbarItems={DEFAULT_EDITOR_TOOLBAR_ITEMS}
/>;

Controlled HTML

const [html, setHtml] = useState('<p>Hello</p>');

<NativeRichTextEditor
  value={html}
  onContentChange={setHtml}
/>;

Controlled JSON

const [doc, setDoc] = useState<DocumentJSON>({
  type: 'doc',
  content: [{ type: 'paragraph', content: [{ type: 'text', text: 'Hello' }] }],
});

<NativeRichTextEditor
  valueJSON={doc}
  onContentChangeJSON={setDoc}
/>;

If your app recreates equivalent JSON objects on rerender, pair valueJSON with valueJSONRevision so the editor can skip redundant work.

Collaboration

In collaboration mode, do not treat valueJSON like ordinary app-owned controlled state.

Instead, bind the editor directly to useYjsCollaboration():

const collaboration = useYjsCollaboration({
  documentId: 'doc-123',
  createWebSocket: () => new WebSocket('wss://example.com/yjs/doc-123'),
  localAwareness: {
    userId: 'u1',
    name: 'Jayden',
    color: '#0A84FF',
  },
});

<NativeRichTextEditor
  valueJSON={collaboration.editorBindings.valueJSON}
  onContentChangeJSON={collaboration.editorBindings.onContentChangeJSON}
  onSelectionChange={collaboration.editorBindings.onSelectionChange}
  onFocus={collaboration.editorBindings.onFocus}
  onBlur={collaboration.editorBindings.onBlur}
  remoteSelections={collaboration.editorBindings.remoteSelections}
/>;

Do not keep a second app-level JSON document state and feed that into valueJSON at the same time. In collaboration mode, the collaboration controller is the source of truth.

Height Behavior

By default, the editor uses heightBehavior="autoGrow" and expands to fit its content. That is usually the right choice when the editor lives inside a parent ScrollView or another screen-managed scrolling container.

If you want the editor to keep a fixed frame and scroll internally, switch to fixed:

<NativeRichTextEditor
  initialContent="<p>Hello</p>"
  heightBehavior="fixed"
/>;

If the parent screen already uses KeyboardAvoidingView or another keyboard-aware layout, keep that in place. On Android with toolbarPlacement="keyboard", make sure any keyboardVerticalOffset covers your own screen chrome and the native toolbar.

Where To Go Next

Clone this wiki locally