Generated JSX should not define component on rerender
See original GitHub issueInitial checklist
- I read the support docs
- I read the contributing guide
- I agree to follow the code of conduct
- I searched issues and couldn’t find anything (or linked relevant results below)
Affected packages and versions
^2.0.0-rc.2 (#1810)
Link to runnable example
No response
Steps to reproduce
Run the following script:
import { compile } from '@mdx-js/mdx';
const { value } = await compile('hello', { jsx:true })
console.log(value)
The jsx
option is there for readability of the output. The issue applies if it’s either true or false.
Expected behavior
The output doesn’t define a component inside a component. This is a React anti-pattern, because it causes unnecessary rerenders
The following Google search yields some articles that explain why this is a bad idea in React: https://www.google.com/search?q=react+create+component+inside+component. Example article: https://dev.to/borasvm/react-create-component-inside-a-component-456b. The same principles apply to Preact.
Earlier the output was ok:
/*@jsxRuntime automatic @jsxImportSource react*/
function MDXContent(props = {}) {
const _components = Object.assign({
p: "p"
}, props.components), {wrapper: MDXLayout} = _components;
const _content = <><_components.p>{"hello"}</_components.p></>;
return MDXLayout ? <MDXLayout {...props}>{_content}</MDXLayout> : _content;
}
export default MDXContent;
Alternative output that’s also ok for React/Preact, although I recall it’s troublesome for some frameworks:
/*@jsxRuntime automatic @jsxImportSource react*/
import { Fragment as _Fragment } from 'react/jsx-runtime.js'
function MDXContent(props = {}) {
const _components = Object.assign({
p: "p"
}, props.components), {wrapper: MDXLayout = _Fragment} = _components;
return <MDXLayout><_components.p>{"hello"}</_components.p></MDXLayout>;
}
export default MDXContent;
Actual behavior
The output does define a component inside a component, which causes unnecessary rerenders.
/*@jsxRuntime automatic @jsxImportSource react*/
function MDXContent(props = {}) {
const {wrapper: MDXLayout} = props.components || ({});
return MDXLayout ? <MDXLayout {...props}><_createMdxContent /></MDXLayout> : _createMdxContent();
function _createMdxContent() {
const _components = Object.assign({
p: "p"
}, props.components);
return <_components.p>{"hello"}</_components.p>;
}
}
export default MDXContent;
Runtime
Node v16
Package manager
npm v8
OS
Linux
Build and bundle tools
Other (please specify in steps to reproduce)
Issue Analytics
- State:
- Created a year ago
- Comments:7 (7 by maintainers)
Top GitHub Comments
This sounds like a whole, unrelated, problem. Perhaps you can open a discussion and share much more about what you want to do.
I don’t understand why
MDXPType
is added instead of the new MDX AST nodes. You can walk the AST, “split” on thematic breaks, and check if there’s only one thing in them, in which case you wrap content into a JSX element.I completely agree and was looking for a way to perform this as a rehype/recma plugin, but couldn’t figure out how to do the following:
Users of my library can mark components as being a “slide layout component”. If such a component is used as the sole component of a slide (between 2
<hr>
), then I consider that to be a slide. If there are multiple components between the<hr>
tags or the component is not defined as a layout, then I wrap the content in a default slide layout.The way I mark a component as a slide layout is by adding a property
MDXPType
on the function and thus I think I can only perform this check at runtime. The only way I can think of to do this with unified at build time, is to either pass a list of slide layouts to the plugin or have a rule that slide layout components should always end in “SlideLayout”. I don’t really like either of these options and thus implemented my parsing at runtime in the client 😅