react-markdown icon indicating copy to clipboard operation
react-markdown copied to clipboard

Dynamic children break the component

Open jonathandewitt-dev opened this issue 1 year ago • 9 comments

Initial checklist

Affected packages and versions

react-markdown

Link to runnable example

No response

Steps to reproduce

Occasionally, through some whimsical magic, React will choose to render a single string passed to children as an array containing one string. I can't seem to reproduce this scenario reliably, it just happens at what seems like random times.

This being the case, however, means that there are valid markdown strings being passed as children which this component is not resilient enough to handle. I keep getting variations of the error message:

Unexpected value `test` for `children` prop, expected `string`

Where "test" was simply my text string passed as the sole child to the Markdown component. Upon inspection, I was able to discover that my string was being converted to string[] and then rejected by the package. Not a great experience because there's quite literally nothing that can be done within my control when facing this situation.

I was able to fix the problem by temporarily modifying this library's code in my node_modules, from this:

  if (typeof children === 'string') {
    file.value = children
  } else {
    unreachable(
      'Unexpected value `' +
        children +
        '` for `children` prop, expected `string`'
    )
  }

to this:

  const allChildrenAreStrings = Array.isArray(children) ? children.every((child) => typeof child === 'string') : typeof children === 'string';

  if (allChildrenAreStrings) {
    file.value = Array.isArray(children) ? children.join('\n') : children
  } else {
    unreachable(
      'Unexpected value `' +
        children +
        '` for `children` prop, expected `string`'
    )
  }

Expected behavior

It should handle string[] children by confirming that each child is indeed of type string and then Array.join('\n') them to continue processing the markdown.

Actual behavior

For string[] arrays, it reports an error and crashes the app.

Runtime

No response

Package manager

No response

OS

No response

Build and bundle tools

No response

jonathandewitt-dev avatar Aug 25 '24 22:08 jonathandewitt-dev

Hi!

children is the value you pass in. Sometimes through JSX. But it’s your choice how to pass it. You can pass weird things: <Markdown>{1}{2}{3}</Markdown>.

It will always be a simple string, if you are explicit, like is shown in the readme: <Markdown children={`# some string`} /> or <Markdown>{`# some string`}</Markdown>.

wooorm avatar Aug 26 '24 09:08 wooorm

Hey there :wave:

So I think there's a misunderstanding here. The string value we pass in is certainly not always interpreted as a string, which is why I'm saying there is valid markdown text not supported by this package. We have no control over how our simple strings get parsed into JSX under the hood.

Here's some evidence I'm telling God's honest truth. :slightly_smiling_face:

the type being passed: image

the error: image

the type evaluated at runtime: image

the runtime type after it's been transformed by React/JSX: image

jonathandewitt-dev avatar Aug 28 '24 03:08 jonathandewitt-dev

On a side note, I don't think it's adding all that much ambiguity to accept a list of strings, regarding https://github.com/remarkjs/react-markdown/pull/855#issuecomment-2309679735

Due to the way JSX works, I think it would be perfectly acceptable to support mapped children (although I don't know why you would need to map over children, but it seems like something we shan't judge if it's easy enough to permit it.) As long as the array passed in is checked to be sure each item in the array is only a string, I don't see how it could introduce much confusion.

jonathandewitt-dev avatar Aug 28 '24 03:08 jonathandewitt-dev

(edit: sorry, pressed enter too fast, done:)

The string value we pass in is certainly not always interpreted as a string

which is why I'm saying there is valid markdown text not supported by this package.

I strongly doubt the first statement.

We have no control over how our simple strings get parsed into JSX under the hood.

Yes, I strongly believe that you do.

Here's some evidence I'm telling God's honest truth. 🙂

Your evidence is: TypeScript says it’s so. But TypeScript lies. TypeScript isn’t real. It’s an approximation of reality. Which is sometimes different from reality.

My evidence to contradict you:

  1. billions of people use this package, and do not experience what you see.
  2. you cannot create a reproduction of this problem

Unhandled runtime error

Can you please wrap your code to throw some error if props.markdown is not a string first?

Can you otherwise make a reproduction?

wooorm avatar Aug 28 '24 09:08 wooorm

Your evidence is: TypeScript says it’s so. But TypeScript lies. TypeScript isn’t real

That is not the totality of my evidence. See the final two screenshots. Runtime doesn't lie.

jonathandewitt-dev avatar Aug 28 '24 12:08 jonathandewitt-dev

Here's some additional evidence if you're still not convinced.

image

image

jonathandewitt-dev avatar Aug 28 '24 13:08 jonathandewitt-dev

Right, thanks. I also mentioned some other points though. Why would nobody else see this? Why could you not make a reproduction? My next assumption, as nobody else sees this, is that you have an uncommon JSX transform. Or a custom JSX runtime. Are there other things you can do to make the problem occur or disappear?

wooorm avatar Aug 28 '24 14:08 wooorm

Rest assured, I'm desperately trying to identify the root cause, because I find it as bizarre and unbelievable as you do. I'll let you know if and when I identify what's different about my scenario that confuses the transformer and causes it to become an array. There's nothing special about my project, it's just an ordinary Next.js project with all the default babel transforms OOTB.

I will also say this is not the first time I've encountered this (intermittent) error, but it's the first time I decided to contribute rather than hack around it or shop around for a different package - which is what I assume is happening for others, hence the silence around the matter.

Putting aside the reproduction, (which I am actively attempting to isolate on the side,) I wonder more about the reasoning behind the resistance to the change? Do you believe accepting string[] is going to introduce problems for users?

jonathandewitt-dev avatar Aug 28 '24 14:08 jonathandewitt-dev

There’s lots of things that can be thought up, such as: bugs need to be squashed, not shoved under the rug. Stuff like that. Any bug that cannot be reproduced is highly suspicious. Any code that is not needed, or cannot be tested correctly, cannot be maintained well, is a problem for users. I don’t want to maintain code in 10 years that I do not understand.

wooorm avatar Aug 28 '24 15:08 wooorm

I've got a similar issue just now.

~~In my case the problem was that the text that I was fetching from my endpoint was too long. Once I reduced the length of the string that I was dynamically passing to the <ReactMarkdown /> element everything worked again.~~

L.E. The issue was that in my long text I had an email address like so: "[email protected]" ... the issue went away when I added a space anywhere in the email address

sorintamas90 avatar Sep 02 '24 14:09 sorintamas90

How could that be an issue? Can you please provide exactly how you are using react-markdown.

wooorm avatar Sep 02 '24 14:09 wooorm

How could that be an issue? Can you please provide exactly how you are using react-markdown.

the following code is crashing my app <ReactMarkdown children="[email protected]" remarkPlugins={[remarkGfm]} />

as I previously said, if I add a space anywhere in the email address the code works fine

see the error bellow image

L.E. apparently it has something to do with the remarkGfm plugin itself.

sorintamas90 avatar Sep 03 '24 06:09 sorintamas90

Thanks for more info. So you don’t have dynamic children, which is what this issue is about. Your problem seems to be Parcel being broken: https://github.com/remarkjs/react-markdown/issues/747#issuecomment-1674930974. Hiding these comments so we can focus on the OP.

wooorm avatar Sep 03 '24 08:09 wooorm

@wooorm

Alright, I figured it out. It's not an intermittent JSX behavior after all. It's due to my package react-shadow-scope emulating DSD with React.Children.map() here. All I need to do is add something like resultChildren.length === 1 ? resultChildren[0] : resultChildren and it works.

So I admit it, it's an upstream issue, not a problem with JSX or this package. :slightly_smiling_face:

(Though I still think it's a harmless addition that may avoid such weirdness in the future, but that's your discretion)

jonathandewitt-dev avatar Sep 06 '24 00:09 jonathandewitt-dev

Thanks but no thanks!

wooorm avatar Sep 09 '24 09:09 wooorm

Hi! This was closed. Team: If this was fixed, please add phase/solved. Otherwise, please add one of the no/* labels.

github-actions[bot] avatar Sep 09 '24 09:09 github-actions[bot]

Hi team! Could you describe why this has been marked as external?

Thanks, — bb

github-actions[bot] avatar Sep 09 '24 09:09 github-actions[bot]