Skip to content
An example of how to use React components as NodeViews for ProseMirror
TypeScript HTML
Branch: master
Clone or download

Latest commit

John Kueh
John Kueh Update readme
Latest commit b12f917 Mar 21, 2020

Files

Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
.vscode Can update editor state Mar 20, 2020
public Updated readme Mar 21, 2020
src Add image with caption example Mar 21, 2020
.gitignore Initialize project using Create React App Mar 20, 2020
README.md
package.json Refactoring Mar 21, 2020
tsconfig.json Initialize project using Create React App Mar 20, 2020
yarn.lock Refactoring Mar 21, 2020

README.md

ProseMirror React NodeViews

This is an example repo of how to use React FC components as NodeViews for ProseMirror

Screenshot

How to use:

Wrap your root component with ReactNodeViewPortalsProvider

Lets use React portals to preserve your app context (css-in-js, data, etc) when the NodeViews are rendered. ReactNodeViewPortalsProvider is a convenient way to help you with this.

import { createReactNodeView } from "./ReactNodeView";
import ReactNodeViewPortalsProvider from "./ReactNodeViewPortals";

const App: React.FC<Props> = props => {
  return (
    <ReactNodeViewPortalsProvider>
      <App {...props} />
    </ReactNodeViewPortalsProvider>
  );
};

export default App;

Loading ProseMirror with React components

This is how you initialize your ProseMirror editor

import React from "react";
import { useReactNodeViewPortals } from "./ReactNodeViewPortals";

const ProseMirror: React.FC<Props> = () => {
  const { createPortal } = useReactNodeViewPortals();
  const editorViewRef = useRef(null);

  const handleCreatePortal = useCallback(createPortal, []);
  const state = useMemo(() => {
    const doc = schema.nodeFromJSON(YOUR_PROSEMIRROR_SCHEMA);
    return EditorState.create({
      doc,
      plugins: [
        history(),
        keymap({ "Mod-z": undo, "Mod-y": redo }),
        keymap(baseKeymap)
      ]
    });
  }, []);

  const createEditorView = useCallback(
    editorViewDOM => {
      const view = new EditorView(editorViewDOM, {
        state,
        nodeViews: {
          heading(node, view, getPos, decorations) {
            return createReactNodeView({
              node,
              view,
              getPos,
              decorations,
              component: Heading,
              onCreatePortal: handleCreatePortal
            });
          },
          paragraph(node, view, getPos, decorations) {
            return createReactNodeView({
              node,
              view,
              getPos,
              decorations,
              component: Paragraph,
              onCreatePortal: handleCreatePortal
            });
          }
        },
        dispatchTransaction(transaction) {
          const newState = view.state.apply(transaction);
          handleChange(newState.doc.toJSON());
          view.updateState(newState);
        }
      });
    },
    [state, handleChange, handleCreatePortal]
  );

  useEffect(() => {
    const editorViewDOM = editorViewRef.current;
    if (editorViewDOM) {
      createEditorView(editorViewDOM);
    }
  }, [createEditorView]);

  return <div ref={editorViewRef}></div>;
};

export default ProseMirror;

Getting node props within your React components

Each of the React components have been wrapped with a context provider before sending it through the portal, so its easy to access the nodeview's props:

import { Heading } from "@chakra-ui/core";
import React from "react";
import { useReactNodeView } from "./ReactNodeView";

const HeadingBlock: React.FC = ({ children }) => {
  const context = useReactNodeView();
  const level = context.node?.attrs.level;
  return <Heading fontSize={`${7 - level}xl`}>{children}</Heading>;
};

export default HeadingBlock;
import { Box, Image, Text } from "@chakra-ui/core";
import React from "react";
import { useReactNodeView } from "./ReactNodeView";

const ImageBlock: React.FC = () => {
  const context = useReactNodeView();
  const attrs = context.node?.attrs;
  return (
    <Box>
      <Image alt={attrs?.alt} src={attrs?.src} />
      {attrs?.title && (
        <Text mt={2} color="gray.500" textAlign="center" fontSize="xs">
          {attrs.title}
        </Text>
      )}
    </Box>
  );
};

export default ImageBlock;
You can’t perform that action at this time.