Skip to content

Migration Guide

Jayden Smith edited this page Sep 8, 2026 · 5 revisions

Migration Guide

Use this guide when upgrading React Native Rich Text Editor. Migration instructions are grouped by version transition and cover package names, public APIs, document schemas, and native rebuilds.

Upgrading from 1.0.3 to 2.x

The instructions below cover changes from 1.0.3 to 2.x; select matching package versions when upgrading. Document handles, native Yjs transport, the default snake_case list names, and the iOS 16.4 / Android API 24 minimums already existed in 1.0.3.

Upgrade checklist

  1. Replace the package dependency and imports, and update the Expo plugin entry. Keep only the new package installed.
  2. Migrate theme entries and addon configuration using the mappings below. Rename removed public exports; component aliases can be migrated separately.
  3. Validate saved JSON against the intended schema and plan the room/snapshot migration before connecting upgraded clients.
  4. If using syntax highlighting, install the extension version matching the editor exactly.
  5. Regenerate Expo native projects or update the existing bare native projects and CocoaPods dependencies, then rebuild both platforms. A JavaScript-only update cannot install these native changes.
  6. Check persisted content, undo/redo, mentions, custom atoms, list spacing, and collaboration against the upgraded app.

Package and public API names

Replace @apollohg/react-native-prose-editor with @apollohg/react-native-rich-text-editor in dependencies, imports, and the Expo config plugin. Keep only one installed package name, regenerate native projects where appropriate, and rebuild the native application. The rename retains the native module names. See Installation.

Use RichTextEditor and RichTextViewer, and their corresponding RichTextEditor* / RichTextViewer* prop, ref, and event types. NativeRichTextEditor, NativeProseViewer, and their component-specific types remain deprecated aliases.

Public document, snapshot, collaboration, and error exports drop V2. Unlike the deprecated component names, the old V2 exports are removed. The complete root-export mapping is:

Previous export Current export
NativeEditorV2CreateConfig NativeEditorCreateConfig
NativeEditorV2EditorState NativeEditorState
NativeEditorV2AtomicRenderSnapshot NativeEditorAtomicRenderSnapshot
NativeEditorV2Initialization NativeEditorInitialization
NativeEditorV2PeerInfo NativeEditorPeerInfo
NativeEditorV2SnapshotMetadata NativeEditorSnapshotMetadata
NativeEditorV2RoomSnapshot NativeEditorRoomSnapshot
NativeEditorV2ErrorBase NativeEditorErrorBase
NativeEditorV2BoundaryError NativeEditorEngineBoundaryError
NativeEditorV2DocumentError NativeEditorDocumentError
NativeEditorV2OperationError NativeEditorOperationError
NativeEditorV2LifecycleError NativeEditorLifecycleError
NativeEditorV2SnapshotError NativeEditorSnapshotError
NativeEditorV2TransportError NativeEditorTransportError
NativeEditorV2NonRetryableError NativeEditorNonRetryableError
NativeEditorV2Error NativeEditorError
NATIVE_EDITOR_V2_NON_RETRYABLE_CODES NATIVE_EDITOR_NON_RETRYABLE_CODES

NativeEditorBoundaryError still denotes the separate prop-resolution boundary error. Internal native/FFI v2 names remain implementation details. See Document API Reference and Production Limits and Errors.

Element styles replace the previous theme tokens

EditorStyleSheet.create() validates a typed map of document elements. Each element accepts an object or nested style arrays with conditional entries. Text styles inherit; padding, margins, backgrounds, and borders belong to their elements. React Native style continues to control the outer view.

import { EditorStyleSheet } from '@apollohg/react-native-rich-text-editor';

const theme = EditorStyleSheet.create({
    content: { padding: 16, backgroundColor: '#ffffff' },
    text: { color: '#1f2937', fontSize: 16, lineHeight: 24 },
    paragraph: { marginBottom: 12 },
    h1: { fontSize: 30, fontWeight: 700 },
    link: { color: '#2563eb', textDecorationLine: 'underline' },
    blockquote: {
        backgroundColor: '#f8fafc',
        borderLeftWidth: 4,
        borderLeftColor: '#94a3b8',
        padding: 12,
        marginVertical: 12,
    },
    codeBlock: {
        color: '#e2e8f0',
        backgroundColor: '#0f172a',
        padding: 12,
        borderRadius: 8,
        marginBottom: 12,
    },
    image: { backgroundColor: '#f1f5f9', borderRadius: 12 },
});

Translate existing themes explicitly; the old shape is no longer accepted:

Previous token Current style
contentInsets.top/right/bottom/left content.paddingTop/Right/Bottom/Left
Root backgroundColor / borderRadius content.backgroundColor / content.borderRadius
placeholderColor placeholder.color
headings.h1 … headings.h6 h1 … h6
links, with underline link, with textDecorationLine: 'underline' or 'none'
Text spacingAfter Block marginBottom
blockquote.text / codeBlock.text Typography directly on blockquote / codeBlock
Blockquote leading borderWidth / borderColor Physical borderLeftWidth / borderLeftColor, or the desired side
Blockquote indent / markerGap Explicit block padding and margins
list.indent / list.baseIndentMultiplier Each of bulletList, orderedList, and taskList
list.spacingAfter / list.itemSpacing List marginBottom / listItem and taskItem margins
list.markerColor / markerScale / markerGap / orderedMarker listMarker.color / scale / gap / ordered
horizontalRule.color / thickness / verticalMargin horizontalRule.backgroundColor / height / marginVertical

toolbar keeps its existing configuration. Move task-checkbox spacing to taskCheckbox.gap; listMarker.gap controls bullet and number spacing. Element names such as bulletList and codeBlock are style keys, independent of the document schema's node names.

Base text.lineHeight now inherits into paragraphs. Recheck layouts that relied on the old exception. Borders support full and per-side widths/colors, corner radii, and solid/dashed/dotted styles. Images, code panels, list containers, placeholders, inline code, and mention chips have their own styles. Mention borders render in both editor and viewer. Justification works on iOS and Android API 26+; Android API 24/25 uses normal alignment.

See Styling and EditorTheme Reference for supported fields, validation, and shorthand precedence. This is a subset of React Native styling; it does not accept arbitrary layout properties or registered numeric style IDs.

Addons are a factory-based array

Replace addons={{ mentions: options }} with addons={[createMentionsAddon(options)]}. Arrays are flat and readonly; false, null, and undefined entries allow conditional features. A view accepts one descriptor per supported capability. Create a new descriptor when options change.

The editor handle still needs withMentionsSchema(...); addons do not change its schema. The viewer composes the standard mention schema automatically. Use theme.mention for shared chip styles, addon theme options for suggestions and chip overrides, and resolveTheme for persisted per-mention styling. See Addons and Mentions.

Optional native code highlighting

The separate package is @apollohg/react-native-rich-text-editor-code-highlighting, a scoped npm package, not a subpath of the core package. Use matching 2.x editor and extension versions to satisfy its editor peer dependency. Import createCodeHighlightingAddon, create a descriptor with a supported theme, and include it in the addon array. Installing it requires a native app rebuild; the core package does not bundle syntect or grammar assets.

Set codeBlock.attrs.language in document JSON to select a supported language. Missing or unsupported languages and blocks exceeding highlighting work limits keep ordinary code styling. A missing native provider is an installation/configuration error. Token colors do not enter document history or collaboration updates. Inline code remains styled through theme.inlineCode.

See Code Syntax Highlighting for installation, languages, themes, and work limits.

Custom atoms and the viewer

Custom atom definitions support typed attributes with runtime constraints and inferred fragment inputs. updateAttrs accepts partial objects, functions of current attributes, or ordered arrays of those updates, and returns a promise with typed failures. Components receive interaction/read-only state, updatePending, updateError, setActive, and editor actions where available. Rendering and attribute updates retain stable atom identity across document changes.

RichTextViewer can mount the same registered React atom components within native prose. Attribute edits are controlled proposals through onUpdateAtomAttrs: persist the requested attributes and supply the updated source through React state. The viewer creates no editable document handle or undo history. Read-only and interaction controls are documented in Viewer, RichTextViewer Reference, and Custom Atom Nodes.

Android now uses an owned text surface and paragraph layout instead of relying on a stock editable widget for block geometry. Registered React atoms are native children in the editor's scroll content, and measured heights update layout in place. Their placement and touch bounds follow native scrolling. This supports the expanded block styling while retaining native input-connection, selection, composition, and accessibility behavior.

Images and authoritative resets

Both native platforms support a bounded, self-contained SVG subset for image sources. Imported image content uses the native image pipeline. Matching loaded images are retained through unrelated full render refreshes, avoiding temporary placeholder heights and scroll jumps. See Links and Images for supported sources and SVG restrictions.

clearContent() and valueJSONUpdateMode="reset" now discard pending native input and provisional composition before applying the authoritative content and clearing history, including a reset when the engine already appears empty. Delayed native input cannot restore the discarded text. Ordinary replacements retain their existing reconciliation behavior. See Content and State and External Text Composition.

Schema and development migration

The built-in codeBlock schema adds language with a null default, even when syntax highlighting is disabled. Declarative attribute constraints also participate in schema fingerprints. Older JSON without language can use the default, but an old binary snapshot does not become compatible merely by upgrading the package.

Before switching schemas, export document JSON with the old schema and validate it under the new one. Migrate the server's room content and create matching snapshots under the new schema, with an explicit decision about retained undo/history and room lineage. Alternatively, keep the exact previous schema explicitly on every client and server until the migration is ready. Do not edit a snapshot's fingerprint to bypass validation or mix clients with incompatible schemas in the same room.

Keep your existing schema naming convention: tiptapCompatibleSchema preserves camelCase list names, but its new language attribute still changes the fingerprint. Preserve language metadata in custom schemas and JSON persistence; the addon itself does not change a schema. See Schema Customization.

TypeScript, Rust, Swift, and Kotlin internals and tests have been split into focused modules. Build, validation, benchmark, example, and native test scripts also use updated names; use Development Workflow for the current commands and the separate highlighting build. Example native projects are generated through Expo Continuous Native Generation.

Awareness limits and recovery

Retained remote awareness identities, including removal clocks, are now bounded to twice limits.collaboration.maxAwarenessPeers. The live-peer limit is unchanged. Long-running peer churn can trigger a retryable TRANSPORT_AWARENESS_LIMIT_EXCEEDED close; the native transport clears retained metadata and reconnects. Do not classify this code alone as permanently incompatible: live-peer or payload-limit violations can still close as incompatible.

Remote removal clocks survive undo/redo, so stale presence cannot reappear after a history operation. Remote awareness JSON must also fit the same parsing depth as peer snapshots; unsupported nesting is rejected atomically instead of disappearing from the peer list. Keep custom awareness state shallow. See Production Limits and Errors.

Clone this wiki locally