This is an example repo of how to use React FC components as NodeViews for ProseMirror
How to use:
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;
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;
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;