Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

React DOM crashes when <option> contains three interpolated value if one is a conditional. #11911

Closed
ZhangYiJiang opened this issue Dec 22, 2017 · 26 comments · Fixed by #13261
Closed

Comments

@ZhangYiJiang
Copy link

ZhangYiJiang commented Dec 22, 2017

Do you want to request a feature or report a bug?

Bug

What is the current behavior?

React DOM crashes when <option> contains three interpolated value if one is a conditional.

Reproduction: https://jsfiddle.net/0opjvycp/

  1. Change the value of the <select>
  2. React crashes with NotFoundError: Node was not found

From what I can tell, the conditional value is necessary, and it must be three interpolated values.

What is the expected behavior?

React should not crash.

Which versions of React, and which browser / OS are affected by this issue? Did this work in previous versions of React?

React DOM 16.2 and 16.0. I think this worked in 15.6 - https://jsfiddle.net/mrwkmuqc/ does not crash

@gaearon
Copy link
Collaborator

gaearon commented Dec 22, 2017

Tagging as good first issue.

@mannanali413
Copy link

@gaearon i can take this up :)

ZhangYiJiang added a commit to nusmodifications/nusmods that referenced this issue Dec 22, 2017
ZhangYiJiang added a commit to nusmodifications/nusmods that referenced this issue Dec 22, 2017
* Add some very basic tests to expose the issue

* Fix bug caused by facebook/react#11911

* Add more tests and fixes
@jorrit
Copy link
Contributor

jorrit commented Dec 24, 2017

Could this be related to #11602 ?

@gaearon
Copy link
Collaborator

gaearon commented Jan 5, 2018

@mannanali413 Any progress?

@kevinzwhuang
Copy link
Contributor

@gaearon I experienced this regression myself yesterday when upgrading from 15.4.2 to 16.2.0.
Here's a simple repro of the problem I was seeing:
https://jsfiddle.net/qkc4eyqe/1/

Same issue - different error message:
Uncaught DOMException: Failed to execute 'removeChild' on 'Node': The node to be removed is not a child of this node.

I'm not sure if @mannanali413 is still working on a fix for this, but I can take a closer look later today too.


To anyone else experiencing this problem, the only workaround I've found so far is to remove the conditional logic (at least until a fix is found).

@gaearon
Copy link
Collaborator

gaearon commented Jan 6, 2018

@kevinzwhuang Would you like to work on figuring out the fix?

@kevinzwhuang
Copy link
Contributor

Sure @gaearon I can work on finding a fix.

Did some digging in devtools
Here's some of my findings:


parentInstance: Note the childNodes is a single element of the flattened text
image

child to be removed - Since there's a mismatch with the flattened childNodes,
image


What we should be seeing is 4 elements in the childNodes array, instead of a single flattened text element - this might have something to do with #11602 like @jorrit mentioned

@avinashdvv
Copy link

@kevinzwhuang can you make a video or a blog post about how did u solve this issue once u solved this.
try to make a video if possible

@kevinzwhuang
Copy link
Contributor

kevinzwhuang commented Jan 8, 2018

Getting closer to finding the root of the problem by running this code with v15 - it looks like what changed from 15/stack to 16/fiber for this operation is that:
in v15, it would execute setTextContent to update the <option> text for all cases.
in v16, it executes removeChild or insertBefore, or whatever variation of the operation where ReactDOM assumes text nodes should be added or removed - when in fact there shouldn't be more than 1 child node because it's flattened in ReactDOMFiberOption (option text nodes were also flattened before in v15, so flattening might be not the issue here)
(whereas this is the normal set of operations for generic components in 15 and 16 because text nodes aren't flattened in other components)

@gaearon - I think the solution has something to do with going back to the old behavior of doing setTextContent for <option> updates - I'm not sure yet how to go about doing this yet, but this is a step closer to finding it.

@gaearon
Copy link
Collaborator

gaearon commented Jan 8, 2018

Sounds good. I really haven't dug into that code but happy to review a PR once you have something that works.

@stephenkingsley
Copy link

stephenkingsley commented Jan 23, 2018

I think function insertBefore has something strange.

insertBefore: function (parentInstance, child, beforeChild) {
  parentInstance.insertBefore(child, beforeChild);
},

That is correct , if I use parentInstance.childNodes[0] instead of beforeChild. Like this:

insertBefore: function (parentInstance, child, beforeChild) {
  parentInstance.insertBefore(child, parentInstance.childNodes[0]);
},

So, in my options, the problem is parentInstance.childNodes not contain beforeChild.

My solution is

// in ReactDOMFiberOption.js file
function flattenChildren(children) {
  let content = document.createElement('span');
  // Flatten children and warn if they aren't strings or numbers;
  // invalid types are ignored.
  // We can silently skip them because invalid DOM nesting warning
  // catches these cases in Fiber.
  React.Children.forEach(children, function(child) {
    if (child == null) {
      return;
    }

    if (typeof child === 'string' || typeof child === 'number') {
      if (typeof children === 'string') {
        content += child;
      } else {
        if (!content) {
          content = document.createElement('span');
        }
        const textNode = document.createTextNode(child);
        content.appendChild(textNode);
      }
      // content += child;
    }
  });

  return content;
}

@stephenkingsley
Copy link

@gaearon would you please code review?

stephenkingsley added a commit to stephenkingsley/react that referenced this issue Jan 24, 2018
reduce option child
@cvlab
Copy link

cvlab commented Mar 31, 2018

I have the same problem with this code

<select>
	{ this.state.option === 'first' ?
		<option>{ '' } a</option>
		:
		<option></option>
	}
</select>

or

<option><br/> a</option>
:
<option></option>

because React try to delete each node from option

@kevinzwhuang my workaround is to use unique keys
https://jsfiddle.net/qkc4eyqe/5/ or https://jsfiddle.net/qkc4eyqe/8/

@itssumitrai
Copy link

Got the same issue with <span>, any update on the fix ?

@dgrcode
Copy link

dgrcode commented Jul 6, 2018

I'm hitting this too

@gaearon
Copy link
Collaborator

gaearon commented Jul 6, 2018

@itssumitrai This issue is about option. If you have a problem with some other tag please file a new issue with a reproducing example.

@dgrcode Knowing that you hit it too doesn't add anything to this conversation. Comments notify everybody subscribed to this thread so it's best to avoid commenting if it doesn't add new info. If you'd like to share more details, or help get it fixed, please let us know!

@gaearon
Copy link
Collaborator

gaearon commented Jul 6, 2018

If someone wants to take another look at #12078 and specifically #12078 (comment) you're most welcome to do it. That will likely lead us closer to fixing this.

@Slowyn
Copy link
Contributor

Slowyn commented Jul 6, 2018

@gaearon Hi Dan! I would like to investigate into this problem and try to help with fix.

@gaearon
Copy link
Collaborator

gaearon commented Jul 6, 2018

Sounds great! This should be a good starting point: #12078 (comment)

@Slowyn
Copy link
Contributor

Slowyn commented Jul 12, 2018

@gaearon Hi Dan!
I did some research and that's what I have at the moment. Basically, I just confirm @kevinzwhuang conclusions.

  1. A fiber of text inside of option contains stateNode without a parentNode.
  2. It happens because of flattening children into an option.
  3. this bug isn't reproduced if I replace children flattening with filtering an array. But this produce new bugs and tests are failed, so it doesn't fit as a decision.
  4. Also, the bug isn't reproduced when you use a construction like this {condition ? 'renderString' ''}. I mean if not use null or false ({condition && 'renderString'}).

I think the easiest solution would be a changing of flattening (or replacing) in a right way. As I get it an option expects one string as a child, but I'm not sure why we need it now when fiber landed 🤔

By the way, it's cool to dip into react sources :)

@gaearon
Copy link
Collaborator

gaearon commented Jul 12, 2018

I think the easiest solution would be a changing of flattening (or replacing) in a right way.

Sounds good to me, wanna try it?

@Slowyn
Copy link
Contributor

Slowyn commented Jul 12, 2018

Yeah, of course!

@Slowyn
Copy link
Contributor

Slowyn commented Jul 22, 2018

I have tried to change flattening, but it didn't fix all problems and became a reason for new issues.
Flattening cause different states. DOM has flattened children, while fiber contains all children.
The other possible solution is to allow react to set text content to option as it does to a textarea. It solves all problems, but there is no warning about incorrect children inside because validateDomNesting is called.

@Slowyn
Copy link
Contributor

Slowyn commented Jul 24, 2018

@gaearon Hi, Dan!
I've created a PR that solves the issue. I've added example into fixtures so it would be easy to test.
Let's discuss this solution :)

Slowyn added a commit to Slowyn/react that referenced this issue Jul 24, 2018
@gaearon
Copy link
Collaborator

gaearon commented Aug 1, 2018

Fixed by @Slowyn

gaearon pushed a commit that referenced this issue Aug 1, 2018
* Make option children a text content by default

fix #11911

* Apply requested changes

- Remove meaningless comments
- revert scripts/rollup/results.json

* remove empty row

* Update comment

* Add a simple unit-test

* [WIP: no flow] Pass through hostContext

* [WIP: no flow] Give better description for test

* Fixes

* Don't pass hostContext through

It ended up being more complicated than I thought.

* Also warn on hydration
segoddnja pushed a commit to segoddnja/react that referenced this issue Aug 1, 2018
* Make option children a text content by default

fix facebook#11911

* Apply requested changes

- Remove meaningless comments
- revert scripts/rollup/results.json

* remove empty row

* Update comment

* Add a simple unit-test

* [WIP: no flow] Pass through hostContext

* [WIP: no flow] Give better description for test

* Fixes

* Don't pass hostContext through

It ended up being more complicated than I thought.

* Also warn on hydration
@krishnaTORQUE
Copy link

@gaearon I experienced this regression myself yesterday when upgrading from 15.4.2 to 16.2.0.
Here's a simple repro of the problem I was seeing:
https://jsfiddle.net/qkc4eyqe/1/

Same issue - different error message:
Uncaught DOMException: Failed to execute 'removeChild' on 'Node': The node to be removed is not a child of this node.

I'm not sure if @mannanali413 is still working on a fix for this, but I can take a closer look later today too.

To anyone else experiencing this problem, the only workaround I've found so far is to remove the conditional logic (at least until a fix is found).

after update 16.13.1
I am getting the same error

does anyone found any solution ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment