let's say I have const Content = mdx.sync('# Hello');
Now how can I render the content into react?
I tried with render content as a component
An internal error occured!
Error: Failed to execute 'createElement' on 'Document': The tag name provided ('/* @jsx mdx */
const makeShortcode = name => function MDXDefaultShortcode(props) {
console.warn("Component " + name + " was not imported, exported, or provided by MDXProvider as global scope")
return <div {...props}/>
};
const layoutProps = {
};
const MDXLayout = "wrapper"
export default function MDXContent({
components,
...props
}) {
return <MDXLayout {...layoutProps} {...props} components={components} mdxType="MDXLayout">
<h1>{`Hello MDX`}</h1>
</MDXLayout>;
}
;
MDXContent.isMDXComponent = true;') is not a valid name.
at createElement (http://localhost:3000/main.js:29915:38)
at createInstance (http://localhost:3000/main.js:31111:24)
at completeWork (http://localhost:3000/main.js:39602:32)
at completeUnitOfWork (http://localhost:3000/main.js:42073:30)
at performUnitOfWork (http://localhost:3000/main.js:42280:16)
at workLoop (http://localhost:3000/main.js:42291:28)
at renderRoot (http://localhost:3000/main.js:42371:11)
at performWorkOnRoot (http://localhost:3000/main.js:43328:11)
at performWork (http://localhost:3000/main.js:43238:11)
at performSyncWork (http://localhost:3000/main.js:43212:7)
MDX creates a string of JavaScript code that needs to be evaluated. You can use the runtime to do that, but it’s unsafe.
Do you have more details on what type of project you have and where the MDX is coming from @naivefun?
We have a collection of example projects as well in case that's helpful.
@johno
Basically we wanna integrate React-Static with mdx but dynamically.
We have a file-system based mdx (with front-matter) hierarchy, which will be parsed by front-matter into an object first, then the mdx string need to passed as an object and rendered later in React component. So direct import mdx is not an option.
@wooorm I tried runtime
@naivefun That depends on how you bundle code. E.g., with WebPack you can follow this in a browser to make it work
By adjusting the config as @wooorm mentioned you _should_ be able to get it working dynamically. Let us know if that's not the case, thanks!
hey, sorry for commenting on an old issue, but i'm trying the same sort of thing.
I've got a CMS with a textarea where we allow people to type MDX, which will be saved as is into the db, but, we'd like to show a preview as they type.
I have tried the runtime, but as that's not safe for user-input, it crashes while the editor is typing.
import MDX from '@mdx-js/runtime';
...
const mdxState = `# hello` // <- works, but '<e' crashes the app
<MDX components={shortcodes}>{mdxState}</MDX>
I'm sure i got this technique working using the gatsby-plugin-mdx renderer... but I stashed the code and can't get it working again 🤦♂️
I'm struggling to connect some dots here..
@mdx-js/runtime can render the MDX string, but only if it's without errors,@mdx-js/mdx can transform incomplete MDX strings, but only as far as JSX,gatsby-plugin-mdx takes a string, but only if the JSX is already transformed down to JS.I feel like i'm missing something, but all the tutorials assume you're working with mdx files at build time. While I'm trying to work with an input string at run-time.
Has anyone pieced this together?
--- edit ----
I've added a ErrorBoundary to prevent the crashing, but it's not the greatest UX!
class PreventInputError extends React.Component {
static getDerivedStateFromError() {
return { hasError: true };
}
state = { hasError: false };
render() {
return this.state.hasError ? this.props.source : this.props.children;
}
}
const Preview = React.memo(({ children }) => (
children ? (
<PreventInputError key={children} source={children}>
<MDX>{children}</MDX>
</PreventInputError>
) : null
));
@wooorm Hi, could you explain why @mdx-js/runtime is unsafe to use? Thanks!
It evaluates javascript: https://github.com/mdx-js/mdx/blob/c4cc70957a329f86b8247117f468586d3a9dfbd0/packages/runtime/src/index.js#L43-L52
I'm also very interested by this (MDX at runtime, from an external source like a CMS)
@peter-mouland Did you eventually get it working again? 🤞
I am also very interested in using MDX with a CMS (this will still be a runtime though).
Has anyone been able to get this to work?
I did, but not using MDX though.
Demo: https://v2-mst-aptd-at-lcz-sty.vercel.app/fr/terms
PR: https://github.com/UnlyEd/next-right-now/pull/113
I have managed to get _something_ working.
The mdx input field has a preview area. When the input has valid MDX it renders a pretty preview. When there is invalid MDX it renders just the ugly mdx code. Which means, as the user types MD, or simple text it is fine, as the use types MDX it will break (i.e. at some point you will have an opening tag without a closing tag).
I reduce the likely hood of content editors typing MDX by supplying a toolbar of actions. When an action icon is clicked, the mdx for that icon is copied to the clipboard and inserted into the text area where the cursor is.
markdown-renderer.js
renders the markdown
import React from 'react';
import PropTypes from 'prop-types';
import Markdown from 'markdown-to-jsx';
import shortcodes from './markdown-shortcodes';
const MarkdownRenderer = ({ children, overrides }) => (
<Markdown
options={{
overrides: { ...shortcodes, ...overrides },
}}
>
{children}
</Markdown>
);
MarkdownRenderer.propTypes = {
children: PropTypes.string.isRequired,
overrides: PropTypes.object,
};
MarkdownRenderer.defaultProps = {
overrides: {},
};
export default MarkdownRenderer;
markdown-shortcodes.js
Customises the rendered html to suite our design-system
// these imports are just simple react components which sets the font size, colour, weight etc
import Text from '../design-system/text';
import { Table } from '../design-system/table';
import List from '../design-system/list';
import BlockQuoteWrapper from '../design-system/blockquote';
import Link from '../design-system/link';
import ListWrapper from '../design-system/list';
import TextWrapper from '../design-system/text';
import TableWrapper from '../design-system/table';
import Spacing from '../design-system/spacing';
export default {
h1: TextWrapper({ type: Spacing.types.H1, presets: Text.presets.H1, isHeader: true }),
h2: TextWrapper({ type: Spacing.types.H2, presets: Text.presets.H2, isHeader: true }),
h3: TextWrapper({ type: Spacing.types.H3, presets: Text.presets.H3, isHeader: true }),
h4: TextWrapper({ type: Spacing.types.H4, presets: Text.presets.H4, isHeader: true }),
h5: TextWrapper({ type: Spacing.types.H5, presets: Text.presets.H5, isHeader: true }),
h6: TextWrapper({ type: Spacing.types.H6, presets: Text.presets.H6, isHeader: true }),
a: Link,
blockquote: BlockQuoteWrapper(),
td: Table.Td,
tr: Table.Tr,
th: Table.Th,
table: TableWrapper(),
strong: Text.Strong,
ol: ListWrapper({ type: Spacing.types.OL, isNumbered: true }),
ul: ListWrapper({ type: Spacing.types.UL, isNumbered: false }),
li: List.Item,
p: TextWrapper({ type: Spacing.types.P, presets: Text.presets.BODY1 }),
};
export const SecondaryCta = ArticleCta;
actions.js
a list of editor actions. This list allows the user to 'click' a toolbar icon which then inserts the correct mdx into the text area
import React, { Fragment, useState } from 'react';
import PropTypes from 'prop-types';
import { useDispatch } from 'react-redux';
import { actions as alertActions, constants as alertConstants } from './redux/alerts';
import Drawer from './design-system/drawer';
import lang from '../lib/lang';
import stringToClipboard from '../lib/string-to-clipboard';
import BulletList from './bullet-list';
import NumberList from './number-list';
import PulloutImage from './image';
import BlockQuote from './blockquote';
import Header from './header';
import Anchor from './anchor';
import SecondaryCta from './secondary-cta';
import Table from './table';
import styles from './styles.css';
const getAlertActionPayload = ({ language }) => ({
alertContent: language.mdxActionSuccessAlert,
alertType: alertConstants.SUCCESS,
isDismissible: true,
isBelowHeader: false,
duration: 3000,
});
const Actions = ({ getMdxText }) => {
const dispatch = useDispatch();
const [isDrawerOpen, toggleDrawer] = useState(false);
const [drawerContent, setDrawerContent] = useState('');
const language = lang;
const alertActionPayload = getAlertActionPayload({ language });
const onCopy = (str) => {
stringToClipboard(str);
toggleDrawer(false);
setDrawerContent('');
dispatch(alertActions.showAlert(alertActionPayload));
};
const openDrawer = (content) => {
setDrawerContent(content);
toggleDrawer(true);
};
return (
<Fragment>
<Drawer
isOpen={isDrawerOpen}
isCloseable
hasBackdrop
placement={Drawer.placements.RIGHT}
onClose={() => toggleDrawer(!isDrawerOpen)}
>
{drawerContent}
</Drawer>
<ul className={styles.actionIcons}>
<li>
<Anchor getMdxText={getMdxText} language={language.anchor} />
</li>
<li>
<SecondaryCta getMdxText={getMdxText} language={language.secondaryCta} />
</li>
<li>
<BulletList getMdxText={getMdxText} language={language.bulletList} />
</li>
<li>
<NumberList getMdxText={getMdxText} language={language.numberedList} />
</li>
<li>
<BlockQuote getMdxText={getMdxText} language={language.blockQuote} />
</li>
<li>
<Table onClick={openDrawer} onCopy={onCopy} language={language.table} />
</li>
<li>
<Header onClick={openDrawer} onCopy={onCopy} language={language.header} />
</li>
<li>
<PulloutImage onClick={openDrawer} onCopy={onCopy} language={language.image} />
</li>
</ul>
</Fragment>
);
};
Actions.propTypes = {
getMdxText: PropTypes.func.isRequired,
};
export default Actions;
mdx-indput.jsx
The MDX Component that can be used in the CMS
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import { MDXProvider } from '@mdx-js/react';
import MDX from '@mdx-js/runtime';
import Fields from './form-fields';
import Label from './label';
import shortCodes from './markdown-shortcodes';
import Actions from './actions';
import styles from './styles.css';
class PreventInputError extends React.Component {
static getDerivedStateFromError() {
return { hasError: true };
}
state = { hasError: false };
render() {
// eslint-disable-next-line react/prop-types
return this.state.hasError ? this.props.source : this.props.children;
}
}
// eslint-disable-next-line react/prop-types
const Preview = React.memo(({ children }) => (
<div>
{children && (
<PreventInputError key={children} source={children}>
<div className={styles.preview}>
<MDX>{children}</MDX>
</div>
</PreventInputError>
)}
</div>
));
const MdxInput = ({ value, label, placeholder, ...props }) => {
const [inputValue, setInputValue] = useState({});
const dataId = label === 'Content' ? 'articleContent' : '';
const insertMdxText = (text) => {
const { selectionStart, value: mdxValue } = document.querySelector('#articleContent textarea');
const cursorPosition = selectionStart;
const front = mdxValue.substring(0, cursorPosition);
const back = mdxValue.substring(cursorPosition, mdxValue.length);
const newValue = front + text + back;
setInputValue({ newValue, componentLength: text.length, component: text });
};
return (
<MDXProvider components={{ ...shortCodes }}>
<Label>{label}</Label>
<div data-id="mdx-input">
<Actions getMdxText={insertMdxText} />
<Fields.TextareaInput dataId={dataId} inputValue={inputValue} placeholder={placeholder} {...props}>
{inputValue.newValue}
</Fields.TextareaInput>
</div>
<div className={styles.mdxPlacement} data-id="mdx-output">
<Preview>{value}</Preview>
</div>
</MDXProvider>
);
};
MdxInput.propTypes = {
name: PropTypes.string.isRequired,
label: PropTypes.string,
value: PropTypes.string,
placeholder: PropTypes.string,
hideActions: PropTypes.bool,
};
MdxInput.defaultProps = {
value: '',
label: '',
placeholder: '',
hideActions: false,
};
export default MdxInput;
Most helpful comment
hey, sorry for commenting on an old issue, but i'm trying the same sort of thing.
I've got a CMS with a textarea where we allow people to type MDX, which will be saved as is into the db, but, we'd like to show a preview as they type.
I have tried the runtime, but as that's not safe for user-input, it crashes while the editor is typing.
I'm sure i got this technique working using the gatsby-plugin-mdx renderer... but I stashed the code and can't get it working again 🤦♂️
I'm struggling to connect some dots here..
@mdx-js/runtimecan render the MDX string, but only if it's without errors,@mdx-js/mdxcan transform incomplete MDX strings, but only as far as JSX,gatsby-plugin-mdxtakes a string, but only if the JSX is already transformed down to JS.I feel like i'm missing something, but all the tutorials assume you're working with mdx files at build time. While I'm trying to work with an input string at run-time.
Has anyone pieced this together?
--- edit ----
I've added a ErrorBoundary to prevent the crashing, but it's not the greatest UX!