Skip to content

Instantly share code, notes, and snippets.

What would you like to do?
[ Typescript / React / ProseMirror ]: Put React components into ProseMirror NodeViews
// This assumes Styled Components is in play, as well.
// Here we have the (too simple) React component which
// we'll be rendering content into.
class Underlined extends React.Component<any, any> {
public hole: HTMLElement | null
// We'll put the content into what we render using
// this function, which appends a given node to
// a ref HTMLElement, if present.
public append(node: HTMLElement) {
if (this.hole) {
public render() {
// Just really wanted to prove I could get React AND
// styled-component abilities at the same time.
const UnderlinedText = styled.span`
text-decoration: underline;
// We want to render the content dom node in the styled
// component. So, we set its inner ref (a SC quirk).
return <UnderlinedText innerRef={(node) => (this.hole = node)} />
// This class is our actual interactor for ProseMirror itself.
// It glues DOM rendering, React, and ProseMirror nodes together.
class Underline {
public dom: HTMLElement
public contentDOM: HTMLElement
public ref: React.RefObject<any>
constructor(public node: Node) {
// We'll use this to access our Underlined component's
// instance methods.
this.ref = React.createRef<any>()
// Here, we'll provide a container to render React into.
// Coincidentally, this is where ProseMirror will put its
// generated contentDOM. React will throw out that content
// once rendered, and at the same time we'll append it into
// the component tree, like a fancy shell game. This isn't
// obvious to the user, but would it be more obvious on an
// expensive render?
this.dom = document.createElement('span')
// Finally, we provide an element to render content into.
// We will be moving this node around as we need to.
this.contentDOM = document.createElement('span')
<Underlined ref={this.ref} />,
public update(node: Node) {
return true
// This is the least complex part. Now we've put
// all of our interlocking pieces behind refs and
// instance properties, this becomes the callback
// which performs the actual shell game.
private putContentDomInRef = () => {
// An example of using the NodeView object in an
// editor view.
new EditorView(
nodeViews: {
underline: (node: Node) => new Underline(node)
state: EditorState.createEmpty()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.