Unable to pass an array of objects in as children to a component in v2
adueck opened this issue · comments
Initial 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.2.1, 2.0.0, @mdx-js/rollup
Link to runnable example
Steps to reproduce
Try to render the following mdx
export function RichList ({ children }) {
return <ul>
{children.map(x => <li>{x.name}</li>)}
</ul>;
}
👇 This `RichList` throws an error.
<RichList>{[
{ name: "a" },
{ name: "b" },
{ name: "c" },
]}</RichList>
See code sandbox
Expected behavior
We should be able to pass an array of objects as the children of a component. This was not a problem in @mdx-js/mdx@1.0.20
.
Actual behavior
When an array of objects is given as the children of a component, we get the error:
Could not parse expression with acorn: Unexpected content after expression
Runtime
Node v18
Package manager
yarn v3
OS
macOS
Build and bundle tools
Rollup, Next.js, Vite
There are some rather fine grained rules around what is considered block vs inline content in JSX.
See some recent conversations around this for more context #2194, #2181, #2172, #2053
The issue you are running into is the same, inline vs block conflict, but with a JSX expression instead of text.
In your example
<RichList>{[
{ name: "a" },
{ name: "b" },
{ name: "c" },
]}</RichList>
Rich list is an inline element, because it includes text on the same line as the tag name, after <RichText>
.
You want it to be treated as a pure JSX block, that could be achieved with either:
Moving the inner content to a newline.
<RichList>
{[
{ name: "a" },
{ name: "b" },
{ name: "c" },
]}
</RichList>
or wrapping the section with {}
so it all falls in the same block expression:
{<RichList>{[
{ name: "a" },
{ name: "b" },
{ name: "c" },
]}</RichList>}
The interesting example in my mind is that this:
<List>{[
"a",
"b",
"c",
]}</List>
Somehow gets treated as a block, when it looks like an inline.
That may be a bug, as that potentially should be treated as a inline, thoughts @mdx-js/maintainers?
@ChristianMurphy thank you for your excellent explanation!
This makes sense and now I know how I can adjust my files to make it render. The interesting thing is that I had this pattern:
<RichList>{[
{ name: "a" },
{ name: "b" },
{ name: "c" },
]}</RichList>
in like 50 files in a project where I used this mdx-loader in my build pipeline (which uses @mdx-js/mdx@1.0.20
) and there were no issues.
When migrating to @mdxj-s/mdx@v2 I was so confused as to why this didn't work,
<RichList>{[
{ name: "a" },
{ name: "b" },
{ name: "c" },
]}</RichList>
And why this did:
<RichList contents={[
{ name: "a" },
{ name: "b" },
{ name: "c" },
]} />
I guess that second example would be considered inline because it's just one big self-closing tag?
I guess I was just a little too naive, thinking I could just put in jsx however I wanted. For others like me it might be nice if explanations these kinds of gotchas were a little more up-front in the documentation, because will rush to just start throwing jsx in md files (which is such an amazing thing btw.)
Looking at what is MDX - jsx and then the deviations from JSX it doesn't seem so clear that my RichList
pattern is a no-no. There's this warning about blank space in spans but that doesn't quite spell out the general problem of mixing blocks and inlines. This explanation you gave is perfect and it would be a big help I think to put it in What is MDX? - jsx and deviations from JSX.
In MDX JSX content can either be inline
<div>Hello world</div>
or a block
<div> Hello world </div>
Content cannot be a mix of both.
The syntax error is expected in this case.<div>Hello world </div>
I made a pull request to add this into the documentation.
Big ask, but perhaps a clearer error message from the compiler like "mixture of inline and block" and a reference to the line number where the problem happened would be helpful. I was pretty stumped by this for a while, trying to figure out why my file wouldn't compile.
Hi there!
a) you shouldn’t do, with React, what you show with <RichList>
. React expects strings or React nodes in children
(and also allows numbers and undefined). It does not want you to pass arbitrary objects. It may not warn itself, but there are several lint rules and also types that shows that it shouldn’t be used. You should likely use:
<RichList
list={[
{name: 'a'},
{name: 'b'}
]}
/>
b) when you are going to mix and match MDX, interleaving JSX elements, JS expressions, and markdown, you should follow the rules (https://mdxjs.com/docs/what-is-mdx/#interleaving), put tags on their own lines:
<RichList>
{[
{name: 'a'},
{name: 'b'}
]}
</RichList>
c) I think you are correct that this is a bug.
<a>{[
'b',
{name: 'c'}
]}</a>
The reason for my thinking is that this crashes, but changing {name: 'c'}
to 'c'
works, I raised this in the place that parses this stuff: micromark/micromark-extension-mdx-jsx#9.
a) you shouldn’t do, with React, what you show with
<RichList>
. React expects strings or React nodes inchildren
(and also allows numbers and undefined). It does not want you to pass arbitrary objects.
This is true for intrinsic elements, but not for custom components. React allows you to pass any arbritraty value. For example, context consumers use a function as a child.
It may not warn itself, but there are several lint rules and also types that shows that it shouldn’t be used.
I think you are referring to incorrect types of the children
prop in React.FunctionComponent
in @types/react
17 and below. This was a bug and has been fixed in DefinitelyTyped/DefinitelyTyped#56210 (Removal of implicit children)
The link you show, shows a legacy interface, do you know of a current example?
I know that it is allowed, but it’s still not a good idea as far as I know.
React router 5 supports a function as a child, but this was removed in React 6.
I agree it seems a bit weird to use arbritrary values as children, but it’s not disallowed. AFAIK it’s not even discouraged. The bug in @types/react
taught many people wrong.