use-prosemirror
ProseMirror + React made easy
NOTE: This library is in production at Pony
Messenger. It was open
sourced to contribute to the incredible ProseMirror
ecosystem, and to ensure the best possible experience for
Pony users.
ProseMirror is one of the best rich
text editors out there. Although it is not written in React, its
render model is very similar to React's. It is therefore a great
fit for your React project.
This package lets you bootstrap a minimal, unopinionated React
integration quickly, using modern React best practices.
Unlike other integrations, it:
- Separates state and presentation so you can keep your state
as high up as necessary.
- Allows using
EditorView
props
as React props. - Is written in TypeScript.
Example
A simple example is available on
CodeSandbox.
Please join this discussion if
you are interested in further usage examples.
Installation
npm install --save use-prosemirror
or
yarn add use-prosemirror
This package specifies React and ProseMirror as peer dependencies,
so make sure you have them installed, too.
Usage
This is all you need to get started:
import 'prosemirror-view/style/prosemirror.css';
import React from 'react';
import {schema} from 'prosemirror-schema-basic';
import {useProseMirror, ProseMirror} from 'use-prosemirror';
return function MyEditor() {
const [state, setState] = useProseMirror({schema});
return <ProseMirror state={state} onChange={setState} />;
};
useProseMirror(config)
This hook maintains editor state. It accepts one argument: the
same object as
EditorState.create
.
It follows the same convention as React's useState
and creates
the initial state and returns it along with an update function.
<ProseMirror />
This component wraps ProseMirror's EditorView
. It displays the
editor state provided by useProseMirror()
and dispatches state
updates using the update function. It accepts the following props:
state
— the EditorState
created by useProseMirror
.onChange
— a function that accepts the next state. This can be
the update function returned by useProseMirror
, or some function
that accepts the next state and eventually calls the update
function. Provide this if you do not provide
dispatchTransaction
.style
— (optional) a React style object to pass to the div
containing ProseMirror.className
— (optional) a string of classes you want to pass to the div
containing ProseMirror.editorViewFactory
— (optional) a function that accepts the root
DOM node where the editor will be mounted, DirectEditorProps
and
component props, and returns an EditorView
. Use this if you extend
EditorView
or want to instantiate the EditorView
in some special way.
It also accepts any
EditorProps
.
So you can, for example, set
transformPastedHTML
directly on the component:
<ProseMirror
state={props.state}
onChange={props.onChange}
transformPastedHTML={string => {
console.log('transformPastedHTML', string);
return string;
}}
/>
Note: If passing a value for the nodeViews
prop, its identity
should be stable. If it changes on every render, the editor will
not work correctly. This issue is described in
#1
and an example of using nodeViews
with React is given in
#5.
If you pass
dispatchTransaction
to <ProseMirror />
, you are responsible for applying dispatched
transactions to the existing state and calling the update function
returned by useProseMirror()
. dispatchTransaction
takes
precedence over onChange
, which will not be called if
dispatchTransaction
is provided.
If you pass a ref
, <ProseMirror />
exposes a view
getter to retrieve the underlying EditorView
instance:
const [state, setState] = useProseMirror({schema});
const viewRef = useRef();
return (
<ProseMirror
ref={viewRef}
state={state}
onChange={state => {
setState(state);
console.log(viewRef.current.view.hasFocus());
}}
/>
);
More Info
React and ProseMirror follow the same data flow model. Even though
ProseMirror is not written in React, it fits nicely into a React
project.
From the ProseMirror documentation:
So the editor view displays a given editor state, and when
something happens, it creates a transaction and broadcasts this.
This transaction is then, typically, used to create a new state,
which is given to the view using its updateState method.
This creates a straightforward, cyclic data flow, as opposed to
the classic approach (in the JavaScript world) of a host of
imperative event handlers, which tends to create a much more
complex web of data flows.
Sound familiar? It continues:
It is possible to ‘intercept’ transactions as they are dispatched
with the dispatchTransaction prop, in order to wire this cyclic
data flow into a larger cycle—if your whole app is using a data
flow model like this, as with Redux and similar architectures, you
can integrate ProseMirror's transactions in your main
action-dispatching cycle, and keep ProseMirror's state in your
application ‘store’.
Which is exactly what this module does.