mdx-js / mdx

Markdown for the component era

Home Page:https://mdxjs.com

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Unable to pass an array of objects in as children to a component in v2

adueck opened this issue · comments

Initial checklist

Affected packages and versions

2.2.1, 2.0.0, @mdx-js/rollup

Link to runnable example

https://codesandbox.io/p/sandbox/trusting-noyce-gnt7k9?file=%2Fpages%2Findex.mdx&selection=%5B%7B%22endColumn%22%3A1%2C%22endLineNumber%22%3A1%2C%22startColumn%22%3A1%2C%22startLineNumber%22%3A1%7D%5D

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

https://codesandbox.io/p/sandbox/trusting-noyce-gnt7k9?file=%2Fpages%2Findex.mdx&selection=%5B%7B%22endColumn%22%3A1%2C%22endLineNumber%22%3A1%2C%22startColumn%22%3A1%2C%22startLineNumber%22%3A1%7D%5D

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.

commented

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 in children (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)

commented

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.