Can't call `blocksToHTMLLossy` on document with React custom blocks without instantiating a BlockNoteView
blx opened this issue · comments
Describe the bug
<what's going wrong!?>
I want to render a document of blocks to HTML, "headlessly" ie. without showing the editor. But when some of my custom blocks are React-rendered (defined via createReactBlockSpec
), an error is thrown when exporting to HTML without having instantiated a BlockNoteView
.
blocksToHTMLLossy()
, somewhere down the stack, depends on contentComponent
being set on the inner editor._tiptapEditor
(here), which is done by a useEffect
in EditorContent
that mutates the editor passed in , and EditorContent
doesn't come into play without having a BlockNoteView
.
I think this specific state of affairs was introduced in #641, but I haven't checked whether this would have worked before that.
For now, I'm just rendering a BlockNoteView on a throwaway React root, in order to get the mutation from EditorContent applied, which is fine, but a bit clunky.
I appreciate this is probably not a common use case 🙃
To Reproduce
const toHTML = async (blocks, schema=null) => BlockNoteEditor.create({
initialContent: blocks,
...(schema ? { schema } : {})
}).blocksToHTMLLossy();
// Default schema block works fine:
console.log(await toHTML([{ type: 'paragraph', content: 'heya' }]));
// React-based custom element doesn't work...
const somethingBlock = createReactBlockSpec(
{ type: 'something', content: 'none', propSchema: {} },
{ render() { return <i>A constant something</i> } }
)
const customSchema = BlockNoteSchema.create({ blockSpecs: { something: somethingBlock } })
// ...Throws error:
console.log(await toHTML([{type: 'something'}], customSchema))
// But this works:
const editor = BlockNoteEditor.create({ initialContent: [{type: 'something'}], schema: customSchema });
await new Promise((resolve, reject) => {
const tmpRoot = ReactDOMClient.createRoot(document.createElement('div'));
tmpRoot.render(React.createElement(() => {
useEffect(() => { resolve(); }, []);
return React.createElement(BlockNoteView, { editor });
}));
});
console.log(await editor.blocksToHTMLLossy());
The error thrown is:
Uncaught (in promise) TypeError: t._tiptapEditor.contentComponent is undefined
Ce ReactRenderUtil.ts:13
toExternalHTML ReactBlockSpec.tsx:213
Kt sharedHTMLConversion.ts:70
serializeNodeInner externalHTMLExporter.ts:66
serializeFragment index.js:3273
forEach index.js:250
serializeFragment index.js:3247
Kt sharedHTMLConversion.ts:109
serializeNodeInner externalHTMLExporter.ts:66
serializeFragment index.js:3273
forEach index.js:250
serializeFragment index.js:3247
Jt sharedHTMLConversion.ts:124
exportProseMirrorFragment externalHTMLExporter.ts:79
exportBlocks externalHTMLExporter.ts:90
blocksToHTMLLossy BlockNoteEditor.ts:907
<clear steps to reproduce are super helpful! Best is to provide an online sandbox, click to create one>
Misc
- Node version:
- Package manager:
- Browser:
- I'm a sponsor and would appreciate if you could look into this sooner than later 💖
Thanks for reporting this! Although your workaround should work we definitely need to fix this. When implementing #451, we should take this into account. I think the ideal solution would:
a) Make sure that for simple React blocks, setting up the React Root is not required. blocksToHTMLLossy
should just work in this case. I think ReactRenderUtil
should already cover this, so I'm not sure why this error is actually thrown (might also be recently fixed in #788 )
b) Make it still possible to call blocksToHTMLLossy (and related functions like blocksToMarkdown) in a React tree. This is because custom react blocks might depend on a React Context, so if the consumer wants it should be possible to make these available