How to inject a tree into another tree without getting hydration errors? #2210
-
I'm altering my mdast tree by injecting another tree into it. It's pretty simple and straight forward code. Here is how it looks like: import type { Root } from 'mdast';
import visit from 'unist-util-visit';
const withReplaceSnippets = (snippetTreeMap: Record<string, Root>) => {
return (tree: Root) => {
visit(tree, ['mdxJsxFlowElement', (node: any) => node.name === 'Snippet'], (node: any) => {
if (!node?.attributes) return;
const fileValue = node.attributes.find((attr: any) => attr.name === 'file')?.value;
if (!fileValue) return;
> const desiredSnippetTree = snippetTreeMap[fileValue];
> node.name = undefined;
> node.type = desiredSnippetTree.type;
> node.children = desiredSnippetTree.children;
});
};
}; The const tree = fromMarkdown(snippets[property], {
extensions: [mdxjs()],
mdastExtensions: [mdxFromMarkdown()],
}); I realized that this is causing react hydration issues Does the mdx team have tips for injecting trees? My sneaking suspicion is that it's the position property that is causing the hydration issue although I am not sure. |
Beta Was this translation helpful? Give feedback.
Replies: 1 comment 5 replies
-
First, you’re using TypeScript but casting everything as import type { Root } from 'mdast'
import visit from 'unist-util-visit'
const withReplaceSnippets = (snippetTreeMap: Record<string, Root>) => {
return (tree: Root) => {
visit(tree, (node) => {
if (node.type === 'mdxJsxFlowElement' && node.name === 'Snippet') {
const file = node.attributes.find((attr) => attr.name === 'file')
if (file && typeof file.value === 'string') {
const desiredSnippetTree = snippetTreeMap[file.value]
node.name = undefined
node.type = desiredSnippetTree.type
node.children = desiredSnippetTree.children
}
}
})
}
} Second, you’re replacing the node wrongly: a)
Taking that code, and using the return values as documented in import {visit, SKIP} from 'unist-util-visit'
visit(tree, (node, index, parent) => {
if (parent && index !== null && node.type === 'mdxJsxFlowElement' && node.name === 'Snippet') {
const file = node.attributes.find((attr) => attr.name === 'file')
if (file && typeof file.value === 'string') {
const desiredSnippetTree = snippetTreeMap[file.value]
parent.children.splice(index, 1, ...desiredSnippetTree.children)
return [SKIP, index]
}
}
}) We still have some problems: import type {Root} from 'mdast'
import {visit, SKIP} from 'unist-util-visit'
import {removePosition} from 'unist-util-remove-position'
const remarkMdxInjectSnippets: Plugin<[Record<string, Root>], Root> = (snippetTreeMap) => {
return (tree, file) => {
visit(tree, (node, index, parent) => {
if (parent && index !== null && node.type === 'mdxJsxFlowElement' && node.name === 'Snippet') {
const file = node.attributes.find((attr) => attr.name === 'file')
const name = file?.value
if (typeof name === 'string') {
if (Object.hasOwn(snippetTreeMap, name)) {
const fragment = removePosition(structuredClone(snippetTreeMap[name]))
parent.children.splice(index, 1, ...fragment.children)
return [SKIP, index]
} else {
file.message(
'Cannot expand missing snippet `' + name + '`', node, 'remark-mdx-inject-snippets'
)
}
}
}
})
}
} |
Beta Was this translation helpful? Give feedback.
First, you’re using TypeScript but casting everything as
any
? That removes all the benefits from TypeScript. Refactored as pseudo code (btw, important: also see https://mdxjs.com/packages/remark-mdx/#types).