Hi folks! Some of you may have been following (or even using!) @nytimes/react-prosemirror. Over a year ago now, I posted about exploring a new approach to integrating React and ProseMirror. This new approach has been available on NPM as @nytimes/react-prosemirror@next for quite a while, and an increasingly large number of users have used it as their primary mechanism for integrating React and ProseMirror, including some large production services.
Unfortunately, the New York Times itself is still stuck on v1 of @nytimes/react-prosemirror, and it may be a while before they’re able to upgrade. In the meantime, since I no longer work at the Times, my own needs for the library have diverged a bit from theirs. So last week we agreed that my software collective, Handle with Care, would take over the development of the v2 approach to React ProseMirror. The code now lives on GitHub at handlewithcarecollective/react-prosemirror, and v2 is officially published to NPM as @handlewithcare/react-prosemirror.
What’s new in v2?
Previously, React ProseMirror relied on ProseMirror’s EditorView to manage the DOM for the editor. To integrate it with React, we used React portals to render components into ProseMirror-managed DOM nodes, and a useLayoutEffect to sync state updates with the EditorView instance.
This approach provided some challenges. First, portals have to be rendered into existing, stable DOM nodes, so all React-based custom node views ended up wrapped in extra HTML elements. This made styling and producing valid DOM more challenging than it should be, and introduced corner cases in browser contenteditable implementations. Second, we induced a double render for every ProseMirror update, and during the first render, React-based custom node views will be rendered with the previous state. This is technically another form of the state tearing that this library was designed to prevent, as all other React components will be rendered with the new state!
To overcome these challenges, the new release moves rendering responsibility entirely into React. We disabled the EditorView’s DOM update cycle, and implemented the same update algorithm that prosemirror-view uses with React components. The result is a more idiomatic, React-based library, which doesn’t have any of the issues of the original implementation.
API changes
- The
ProseMirrorcomponent API has changed slightly:- Instead of passing a
mountprop with a ref to a child element, users must render aProseMirrorDoccomponent as a child of theProseMirrorcomponent. - The
nodeViewsprop no longer matches the ProseMirror API. Instead,nodeViewsshould be a map from node type name to React components, each of which must takeNodeViewComponentPropsas props. This map should be memoized, or defined outside the React component entirely. - To pass standard ProseMirror node view constructors, use the
customNodeViewsprop
- Instead of passing a
- The API that React-based node views must implement has changed:
- There is no longer any need to provide a ProseMirror node view constructor function. React-based node views are now just React components that accept
NodeViewComponentPropsas props. - Props related to the ProseMirror node, such as the node itself, the
getPosfunction, and decorations, now live in thenodePropsproperty. All other props must be spread onto the root element of the node view component, aside fromchildren, which may be rendered anywhere in the component, as appropriate. - All node view components must forward their ref to the root element of the component.
- To implement features that would normally live in the node view spec, there are new hooks, such as
useStopEventanduseSelectNode
- There is no longer any need to provide a ProseMirror node view constructor function. React-based node views are now just React components that accept
- There is a new export,
widget, which behaves similarly toDecoration.widgetfromprosemirror-view, but takes a React component instead of atoDOMmethod.
Other fun things
@handlewithcare/react-prosemirrorsupports SSR! If you like, you can server-side render your ProseMirror components. This can be particularly useful if you’d like to render a read-only version of your documents for search indexers or logged out users.@handlewithcare/react-prosemirrorsupports React Strict Mode/Concurrent Mode, and it’s really fast, even for gigantic documents!