Manage children inside DOM properties
Fiber manages children separately so we don't need to do it here. However, we special case the text content children and dangerouslySetInnerHTML. This reveals a bug that we currently don't handle the case where we switch from dangerouslySetInnerHTML or text children to element children, because child insertions are handled before the parent updates. We could possibly handle this case by removing all nodes before the first host child but that is a bit unfortunate.
Sebastian Markbage committed
Nov 15, 2016 at 19:02 UTC
7cd26b9c7172465981b70ea6bf0bb9b09ce8b715
1 file changed
+46
-114
src/renderers/dom/fiber/ReactDOMFiberComponent.js
+46
-114
@@ -16,7 +16,6 @@
16
17
var AutoFocusUtils = require('AutoFocusUtils');
18
var CSSPropertyOperations = require('CSSPropertyOperations');
19
-var DOMLazyTree = require('DOMLazyTree');
19
var DOMNamespaces = require('DOMNamespaces');
20
var DOMProperty = require('DOMProperty');
21
var DOMPropertyOperations = require('DOMPropertyOperations');
@@ -27,13 +26,14 @@ var ReactDOMFiberInput = require('ReactDOMFiberInput');
26
var ReactDOMFiberOption = require('ReactDOMFiberOption');
27
var ReactDOMFiberSelect = require('ReactDOMFiberSelect');
28
var ReactDOMFiberTextarea = require('ReactDOMFiberTextarea');
30
-var ReactMultiChild = require('ReactMultiChild');
29
var ReactServerRenderingTransaction = require('ReactServerRenderingTransaction');
30
31
var emptyFunction = require('emptyFunction');
32
var escapeTextContentForBrowser = require('escapeTextContentForBrowser');
33
var invariant = require('invariant');
34
var isEventSupported = require('isEventSupported');
35
+var setInnerHTML = require('setInnerHTML');
36
+var setTextContent = require('setTextContent');
37
var shallowEqual = require('shallowEqual');
38
var inputValueTracking = require('inputValueTracking');
39
var warning = require('warning');
@@ -46,13 +46,11 @@ var registrationNameModules = EventPluginRegistry.registrationNameModules;
46
// For quickly matching children type, to test if can be treated as content.
47
var CONTENT_TYPES = {'string': true, 'number': true};
48
49
+var DANGEROUSLY_SET_INNER_HTML = 'dangerouslySetInnerHTML';
50
+var SUPPRESS_CONTENT_EDITABLE_WARNING = 'suppressContentEditableWarning';
51
+var CHILDREN = 'children';
52
var STYLE = 'style';
53
var HTML = '__html';
51
-var RESERVED_PROPS = {
52
- children: null,
53
- dangerouslySetInnerHTML: null,
54
- suppressContentEditableWarning: null,
55
-};
54
55
// Node type for document fragments (Node.DOCUMENT_FRAGMENT_NODE).
56
var DOC_FRAGMENT_TYPE = 11;
@@ -363,39 +361,6 @@ function isCustomComponent(tagName, props) {
361
return tagName.indexOf('-') >= 0 || props.is != null;
362
}
363
366
-function createInitialChildren(workInProgress, transaction, props, context, lazyTree) {
367
- // Intentional use of != to avoid catching zero/false.
368
- var innerHTML = props.dangerouslySetInnerHTML;
369
- if (innerHTML != null) {
370
- if (innerHTML.__html != null) {
371
- DOMLazyTree.queueHTML(lazyTree, innerHTML.__html);
372
- }
373
- } else {
374
- var contentToUse =
375
- CONTENT_TYPES[typeof props.children] ? props.children : null;
376
- var childrenToUse = contentToUse != null ? null : props.children;
377
- // TODO: Validate that text is allowed as a child of this node
378
- if (contentToUse != null) {
379
- // Avoid setting textContent when the text is empty. In IE11 setting
380
- // textContent on a text area will cause the placeholder to not
381
- // show within the textarea until it has been focused and blurred again.
382
- // https://github.com/facebook/react/issues/6731#issuecomment-254874553
383
- if (contentToUse !== '') {
384
- DOMLazyTree.queueText(lazyTree, contentToUse);
385
- }
386
- } else if (childrenToUse != null) {
387
- var mountImages = workInProgress.mountChildren(
388
- childrenToUse,
389
- transaction,
390
- context
391
- );
392
- for (var i = 0; i < mountImages.length; i++) {
393
- DOMLazyTree.queueChild(lazyTree, mountImages[i]);
394
- }
395
- }
396
- }
397
-}
398
-
364
/**
365
* Reconciles the properties by detecting differences in property values and
366
* updating the DOM as necessary. This function is probably the single most
@@ -436,15 +401,20 @@ function updateDOMProperties(
401
styleUpdates[styleName] = '';
402
}
403
}
404
+ } else if (propKey === DANGEROUSLY_SET_INNER_HTML ||
405
+ propKey === CHILDREN) {
406
+ // TODO: Clear innerHTML. This is currently broken in Fiber because we are
407
+ // too late to clear everything at this point because new children have
408
+ // already been inserted.
409
+ } else if (propKey === SUPPRESS_CONTENT_EDITABLE_WARNING) {
410
+ // Noop
411
} else if (registrationNameModules.hasOwnProperty(propKey)) {
412
// Do nothing for deleted listeners.
413
} else if (isCustomComponent(workInProgress._tag, lastProps)) {
442
- if (!RESERVED_PROPS.hasOwnProperty(propKey)) {
443
- DOMPropertyOperations.deleteValueForAttribute(
444
- getNode(workInProgress),
445
- propKey
446
- );
447
- }
414
+ DOMPropertyOperations.deleteValueForAttribute(
415
+ getNode(workInProgress),
416
+ propKey
417
+ );
418
} else if (
419
DOMProperty.properties[propKey] ||
420
DOMProperty.isCustomAttribute(propKey)) {
@@ -489,18 +459,40 @@ function updateDOMProperties(
459
// Relies on `updateStylesByID` not mutating `styleUpdates`.
460
styleUpdates = nextProp;
461
}
462
+ } else if (propKey === DANGEROUSLY_SET_INNER_HTML) {
463
+ if (lastProp && nextProp) {
464
+ var lastHtml = lastProp[HTML];
465
+ var nextHtml = nextProp[HTML];
466
+ if (lastHtml !== nextHtml) {
467
+ if (nextHtml == null) {
468
+ // TODO: It might be too late to clear this if we have children
469
+ // inserted already.
470
+ } else {
471
+ setInnerHTML(getNode(workInProgress), '' + nextHtml);
472
+ }
473
+ }
474
+ } else if (nextProp) {
475
+ var nextHtml = nextProp[HTML];
476
+ setInnerHTML(getNode(workInProgress), nextProp);
477
+ }
478
+ } else if (propKey === CHILDREN) {
479
+ if (typeof nextProp === 'string') {
480
+ setTextContent(getNode(workInProgress), nextProp);
481
+ } else if (typeof nextProp === 'number') {
482
+ setTextContent(getNode(workInProgress), '' + nextProp);
483
+ }
484
+ } else if (propKey === SUPPRESS_CONTENT_EDITABLE_WARNING) {
485
+ // Noop
486
} else if (registrationNameModules.hasOwnProperty(propKey)) {
487
if (nextProp) {
488
ensureListeningTo(workInProgress, propKey, transaction);
489
}
490
} else if (isCustomComponentTag) {
497
- if (!RESERVED_PROPS.hasOwnProperty(propKey)) {
498
- DOMPropertyOperations.setValueForAttribute(
499
- getNode(workInProgress),
500
- propKey,
501
- nextProp
502
- );
503
- }
491
+ DOMPropertyOperations.setValueForAttribute(
492
+ getNode(workInProgress),
493
+ propKey,
494
+ nextProp
495
+ );
496
} else if (
497
DOMProperty.properties[propKey] ||
498
DOMProperty.isCustomAttribute(propKey)) {
@@ -524,55 +516,6 @@ function updateDOMProperties(
516
}
517
}
518
527
-/**
528
- * Reconciles the children with the various properties that affect the
529
- * children content.
530
- *
531
- * @param {object} lastProps
532
- * @param {object} nextProps
533
- * @param {ReactReconcileTransaction} transaction
534
- * @param {object} context
535
- */
536
-function updateDOMChildren(workInProgress, lastProps, nextProps, transaction, context) {
537
- var lastContent =
538
- CONTENT_TYPES[typeof lastProps.children] ? lastProps.children : null;
539
- var nextContent =
540
- CONTENT_TYPES[typeof nextProps.children] ? nextProps.children : null;
541
-
542
- var lastHtml =
543
- lastProps.dangerouslySetInnerHTML &&
544
- lastProps.dangerouslySetInnerHTML.__html;
545
- var nextHtml =
546
- nextProps.dangerouslySetInnerHTML &&
547
- nextProps.dangerouslySetInnerHTML.__html;
548
-
549
- // Note the use of `!=` which checks for null or undefined.
550
- var lastChildren = lastContent != null ? null : lastProps.children;
551
- var nextChildren = nextContent != null ? null : nextProps.children;
552
-
553
- // If we're switching from children to content/html or vice versa, remove
554
- // the old content
555
- var lastHasContentOrHtml = lastContent != null || lastHtml != null;
556
- var nextHasContentOrHtml = nextContent != null || nextHtml != null;
557
- if (lastChildren != null && nextChildren == null) {
558
- workInProgress.updateChildren(null, transaction, context);
559
- } else if (lastHasContentOrHtml && !nextHasContentOrHtml) {
560
- workInProgress.updateTextContent('');
561
- }
562
-
563
- if (nextContent != null) {
564
- if (lastContent !== nextContent) {
565
- workInProgress.updateTextContent('' + nextContent);
566
- }
567
- } else if (nextHtml != null) {
568
- if (lastHtml !== nextHtml) {
569
- workInProgress.updateMarkup('' + nextHtml);
570
- }
571
- } else if (nextChildren != null) {
572
- workInProgress.updateChildren(nextChildren, transaction, context);
573
- }
574
-}
575
-
519
var ReactDOMFiberComponent = {
520
521
@@ -716,9 +659,6 @@ var ReactDOMFiberComponent = {
659
DOMPropertyOperations.setAttributeForRoot(el);
660
}
661
updateDOMProperties(workInProgress, null, props, transaction, isCustomComponentTag);
719
- var lazyTree = DOMLazyTree(el);
720
- createInitialChildren(workInProgress, transaction, props, context, lazyTree);
721
- mountImage = lazyTree;
662
663
switch (workInProgress._tag) {
664
case 'input':
@@ -777,7 +717,7 @@ var ReactDOMFiberComponent = {
717
break;
718
}
719
780
- return mountImage;
720
+ return el;
721
},
722
723
@@ -827,13 +767,6 @@ var ReactDOMFiberComponent = {
767
assertValidProps(workInProgress, nextProps);
768
var isCustomComponentTag = isCustomComponent(workInProgress._tag, nextProps);
769
updateDOMProperties(workInProgress, lastProps, nextProps, transaction, isCustomComponentTag);
830
- updateDOMChildren(
831
- workInProgress,
832
- lastProps,
833
- nextProps,
834
- transaction,
835
- context
836
- );
770
771
switch (workInProgress._tag) {
772
case 'input':
@@ -901,7 +834,6 @@ var ReactDOMFiberComponent = {
834
break;
835
}
836
904
- workInProgress.unmountChildren(safely, skipLifecycle);
837
ReactDOMComponentTree.uncacheNode(workInProgress);
838
workInProgress._wrapperState = null;
839
},