React ProseMirror

A fully featured library for safely integrating ProseMirror and React

README

React ProseMirror


React ProseMirror Logo
A fully featured library for safely integrating ProseMirror and React.

Join the chat at https://gitter.im/nytimes/react-prosemirror

Installation


npm:

  1. ```sh
  2. npm install @nytimes/react-prosemirror
  3. ```

yarn:

  1. ```sh
  2. yarn add @nytimes/react-prosemirror
  3. ```

The Problem


React is a framework for developing reactive user interfaces. To make updates
efficient, React separates updates into phases so that it can process updates in
batches. In the first phase, application code renders a virtual document. In the
second phase, the React DOM renderer finalizes the update by reconciling the
real document with the virtual document. The ProseMirror View library renders
ProseMirror documents in a single-phase update. Unlike React, it also allows
built-in editing features of the browser to modify the document under some
circumstances, deriving state updates from view updates rather than the other
way around.

It is possible to use both React DOM and ProseMirror View, but using React DOM
to render ProseMirror View components safely requires careful consideration of
differences between the rendering approaches taken by each framework. The first
phase of a React update should be free of side effects, which requires that
updates to the ProseMirror View happen in the second phase. This means that
during the first phase, React components actually have access to a different
(newer) version of the EditorState than the one in the Editorview. As a result
code that dispatches transactions may dispatch transactions based on incorrect
state. Code that invokes methods of the ProseMirror view may make bad
assumptions about its state that cause incorrect behavior or errors.

The Solution


There are two different directions to integrate ProseMirror and React: you can
render a ProseMirror EditorView inside of a React component, and you can use
React components to render ProseMirror NodeViews. This library provides tools
for accomplishing both of these goals.

Rendering ProseMirror Views within React


This library provides a set of React contexts and hooks for consuming them that
ensure safe access to the EditorView from React components. This allows us to
build React applications that contain ProseMirror Views, even when the
EditorState is lifted into React state, or a global state management system like
Redux.

The simplest way to make use of these contexts is with the ``component. The `` component can be used controlled or
uncontrolled, and takes a "mount" prop, used to specify which DOM node the
ProseMirror EditorView should be mounted on.

  1. ```tsx
  2. import { EditorState } from "prosemirror-state";
  3. import { ProseMirror } from "@nytimes/react-prosemirror";

  4. export function ProseMirrorEditor() {
  5.   // It's important that mount is stored as state,
  6.   // rather than a ref, so that the ProseMirror component
  7.   // is re-rendered when it's set
  8.   const [mount, setMount] = useState<HTMLElement | null>(null);

  9.   return (
  10.     <ProseMirror mount={mount} defaultState={EditorState.create({ schema })}>
  11.       <div ref={setMount} />
  12.     </ProseMirror>
  13.   );
  14. }
  15. ```

The EditorState can also easily be lifted out of the ProseMirror component and
passed as a prop.

  1. ```tsx
  2. import { EditorState } from "prosemirror-state";
  3. import { schema } from "prosemirror-schema-basic";
  4. import { ProseMirror } from "@nytimes/react-prosemirror";

  5. export function ProseMirrorEditor() {
  6.   const [mount, setMount] = useState<HTMLElement | null>(null);
  7.   const [editorState, setEditorState] = useState(
  8.     EditorState.create({ schema })
  9.   );

  10.   return (
  11.     <ProseMirror
  12.       mount={mount}
  13.       state={editorState}
  14.       dispatchTransaction={(tr) => {
  15.         setEditorState((s) => s.apply(tr));
  16.       }}
  17.     >
  18.       <div ref={setMount} />
  19.     </ProseMirror>
  20.   );
  21. }
  22. ```

The ProseMirror component will take care to ensure that the EditorView is always
updated with the latest EditorState after each render cycle. Because
synchronizing the EditorView is a side effect, it _must_ happen in the effects
phase of the React render lifecycle, _after_ all of the ProseMirror component's
children have run their render functions. This means that special care must be
taken to access the EditorView from within other React components. In order to
abstract away this complexity, React ProseMirror provides two hooks:
useEditorEffect and useEditorEventCallback. Both of these hooks can be used
from any children of the ProseMirror component.