# react-prosemirror: making React and ProseMirror agree on when to update

> The New York Times library for ProseMirror and React exists to solve one specific problem, that React renders in phases and ProseMirror renders in one, and the two disagree about which version of the editor state is current, so the library ships hooks that keep every access to the editor view on the right side of the commit.

**nytimes/react-prosemirror** — A library for safely integrating ProseMirror and React.

- Repository: https://github.com/nytimes/react-prosemirror
- Stars: 572 · Forks: 20
- Language: TypeScript
- License: MIT
- Published: 2026-10-22 · Updated: 2026-10-22 · Language: en
- Canonical page: https://hysenlabs.com/projects/nytimes-react-prosemirror

## Why two render models do not mix without help

The problem the library describes is precise, and it is worth understanding before reaching for it. React separates updates into phases so it can batch them. In the first phase, application code renders a virtual document, and in the second phase the React DOM renderer finalizes the update by reconciling the real document with the virtual one. The ProseMirror View library renders ProseMirror documents in a single-phase update, and unlike React it 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. Using both is possible, but doing it safely requires care about the differences between the two rendering approaches, and the specific failure is described next.

## The stale state bug, described precisely

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. The consequence is that during the first phase, React components actually have access to a different and newer version of the EditorState than the one in the EditorView. Code that dispatches transactions may therefore dispatch transactions based on incorrect state, and code that invokes methods of the ProseMirror view may make bad assumptions about its state that cause incorrect behaviour or errors. This is the whole reason the library exists. It is not about making React and ProseMirror feel nicer together, it is about a specific class of bug where a component reads the editor, acts on it, and acts on a version of the document that is one transaction out of date.

## The ProseMirror component, controlled and uncontrolled

The simplest way to use the library's contexts is with the ProseMirror component, which can be used controlled or uncontrolled and takes a mount prop specifying which DOM node the EditorView should be mounted on. In the uncontrolled form you give it a defaultState, and the mount is stored as state rather than a ref, which the example points out matters because the component has to re-render when the mount is set.

```tsx
import { EditorState } from "prosemirror-state";
import { schema } from "prosemirror-schema-basic";
import { ProseMirror } from "@nytimes/react-prosemirror";

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

  return (
    <ProseMirror mount={mount} defaultState={defaultState}>
      <div ref={setMount} />
    </ProseMirror>
  );
}
```

The controlled form lifts the EditorState out and passes it as a prop, with a dispatchTransaction handler that applies the transaction to the current state. That is the shape you want when the document lives in React state or in a store such as Redux.

```tsx
import { EditorState } from "prosemirror-state";
import { schema } from "prosemirror-schema-basic";
import { ProseMirror } from "@nytimes/react-prosemirror";

export function ProseMirrorEditor() {
  const [mount, setMount] = useState<HTMLElement | null>(null);
  const [state, setState] = useState(() => EditorState.create({ schema }));

  return (
    <ProseMirror
      mount={mount}
      state={state}
      dispatchTransaction={(tr) => {
        setState((s) => s.apply(tr));
      }}
    >
      <div ref={setMount} />
    </ProseMirror>
  );
}
```

The ProseMirror component takes care to ensure that the EditorView is always updated with the latest EditorState after each render cycle, and 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.

## The hooks that keep your components on the right side of the commit

Because the synchronisation is a side effect that runs after your children render, special care is needed to access the EditorView from other React components, and the library provides two hooks to abstract that away. useEditorEffect and useEditorEventCallback can both be used from any children of the ProseMirror component. The effect hook is the one for positioning, where you need to place a React component relative to a specific position in the document, for example a widget that has to sit at the user's cursor, and the point of the hook is that the positioning happens when the EditorView is in sync with the latest EditorState rather than at an arbitrary moment in the render. The event callback hook is the one for reacting to editor events without reintroducing the stale-state problem, and there is a separate event listener hook listed in the API section for the same family of needs.

## The other direction: React components as node views

The library covers the second integration direction as well, and the overview states there are two. You can render a ProseMirror EditorView inside a React component, which is what the ProseMirror component and its hooks are for, and you can use React components to render ProseMirror NodeViews. The API section also lists useNodePos, which is the piece that makes the second direction work, because a node view needs to know which position in the document it is bound to, and that position changes as the document is edited. The other entries in the API list are useEditorState, useEditorEventCallback and useEditorEventListener in their non-context forms, so there is a way to consume the editor state directly rather than only through the surrounding component.

## The project has split, and the split is the headline

The most important fact in the project overview is that active development has moved. As of 17 January 2025, development on the v2 release of the library, also known as the react-editor-view branch, moved to the handlewithcarecollective repository and was published under the @handlewithcare/react-prosemirror package. Bug fixes and patches may still be applied to the 1.x branch in this repository and published to @nytimes/react-prosemirror. The stated reasoning is that the intent had been to focus on the upcoming v2 release in the main branch of this repository, but the maintainers decided instead to fork that version into its own repository and npm package. So there are two lines with different homes, and the release tags here stop at v1.0.0 while the package manifest in this repository carries version 2.0.1.

## The build tooling, and what a contributor inherits

The repository is a modern TypeScript project built on swc, with separate CommonJS and ES module outputs and a separate type declaration build, all run in parallel by a build script that fans out over the build sub-scripts. Checks are split the same way, with a format check, a lint check and a type check running concurrently, and a test script that runs jest. There is a demo directory with a menu, a main entry point and a stylesheet, served by vite, and the dev script rebuilds on watch. The package is an ES module with explicit exports for both import and require, and it ships a ./src entry point so consumers can reach the TypeScript sources directly. The repository also carries husky, lint-staged, a prettier configuration, a swc configuration for both module formats, a devcontainer, a security file, a code of conduct and a codeowners file, and the tests are jest with the testing-library packages for React and user events.

## Conclusion

react-prosemirror fits a React team building a rich text editor on ProseMirror, especially one that lifts the EditorState into React state or a store like Redux, because the contexts and hooks exist to keep component code from reading a stale editor state during React's render phase. It does not fit a new project, because the project overview says active development on the v2 line moved on 17 January 2025 to the handlewithcarecollective repository and the @handlewithcare/react-prosemirror package, and only bug fixes are still applied to the 1.x branch here. It also does not fit a team that only needs a plain textarea or a Markdown preview. Before you start, decide which package you are adopting, because the two lines have diverged, and read the section on the ProseMirror component's mount prop, since storing the mount node as state rather than a ref is what makes the component re-render when it is set.

## FAQ

### What problem does react-prosemirror solve?

React renders in two phases while the ProseMirror View library renders in a single phase, so during React's first phase components can see a newer EditorState than the one in the EditorView. Code that dispatches transactions or calls view methods on that state can act on the wrong version, and the library's contexts and hooks keep those accesses on the correct side of the commit.

### Where has the active development of react-prosemirror moved to?

As of 17 January 2025, development on the v2 release moved to the handlewithcarecollective repository and the @handlewithcare/react-prosemirror npm package, because the maintainers decided to fork that version out of this repository. Bug fixes and patches may still be applied to the 1.x branch here and published to @nytimes/react-prosemirror.

### How do I use the ProseMirror component with React?

It can be used controlled or uncontrolled and takes a mount prop naming the DOM node the EditorView is mounted on. In the uncontrolled form you pass a defaultState, in the controlled form you pass state and a dispatchTransaction handler, and in both cases the mount node should be stored as state rather than a ref so the component re-renders when it is set.

### What do useEditorEffect and useEditorEventCallback do?

They are the two hooks that abstract away the need to access the EditorView carefully from inside a React component, and both can be used from any children of the ProseMirror component. useEditorEffect runs an effect when the EditorView is in sync with the latest EditorState, which is how you position a component relative to a document position, and useEditorEventCallback handles editor events without reintroducing the stale state problem.

## Sources

- [Issues](https://github.com/nytimes/react-prosemirror/issues)
- [License: MIT](https://github.com/nytimes/react-prosemirror/blob/main/LICENSE)
- [nytimes/react-prosemirror on GitHub](https://github.com/nytimes/react-prosemirror)
- [README](https://github.com/nytimes/react-prosemirror/blob/main/README.md)
- [Releases](https://github.com/nytimes/react-prosemirror/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/nytimes-react-prosemirror
