18
19
import {
20
getValueForAttribute,
21
+ getValueForAttributeOnCustomComponent,
22
getValueForProperty,
23
setValueForProperty,
24
+ setValueForPropertyOnCustomComponent,
25
} from './DOMPropertyOperations';
26
import {
27
initWrapperState as ReactDOMInputInitWrapperState,
58
validateShorthandPropertyCollisionInDev,
59
} from './CSSPropertyOperations';
60
import {HTML_NAMESPACE, getIntrinsicNamespace} from './DOMNamespaces';
59
-import {
60
- getPropertyInfo,
61
- shouldIgnoreAttribute,
62
- shouldRemoveAttribute,
63
-} from '../shared/DOMProperty';
64
-import assertValidProps from './assertValidProps';
61
+import {getPropertyInfo} from '../shared/DOMProperty';
62
import {DOCUMENT_NODE} from './HTMLNodeType';
63
import isCustomComponent from '../shared/isCustomComponent';
64
import possibleStandardNames from '../shared/possibleStandardNames';
81
let didWarnInvalidHydration = false;
82
let didWarnScriptTags = false;
83
87
-const DANGEROUSLY_SET_INNER_HTML = 'dangerouslySetInnerHTML';
88
-const SUPPRESS_CONTENT_EDITABLE_WARNING = 'suppressContentEditableWarning';
89
-const SUPPRESS_HYDRATION_WARNING = 'suppressHydrationWarning';
90
-const AUTOFOCUS = 'autoFocus';
91
-const CHILDREN = 'children';
92
-const STYLE = 'style';
93
-const HTML = '__html';
94
-
84
let warnedUnknownTags: {
85
[key: string]: boolean,
86
};
118
registrationNameDependencies,
119
possibleRegistrationNames,
120
});
121
+ if (
122
+ props.contentEditable &&
123
+ !props.suppressContentEditableWarning &&
124
+ props.children != null
125
+ ) {
126
+ console.error(
127
+ 'A component is `contentEditable` and contains `children` managed by ' +
128
+ 'React. It is now your responsibility to guarantee that none of ' +
129
+ 'those nodes are unexpectedly modified or duplicated. This is ' +
130
+ 'probably not intentional.',
131
+ );
132
+ }
133
}
134
}
135
298
continue;
299
}
300
const nextProp = nextProps[propKey];
300
- if (propKey === STYLE) {
301
- if (__DEV__) {
302
- if (nextProp) {
303
- // Freeze the next style object so that we can assume it won't be
304
- // mutated. We have already warned for this in the past.
305
- Object.freeze(nextProp);
301
+ switch (propKey) {
302
+ case 'style': {
303
+ if (nextProp != null && typeof nextProp !== 'object') {
304
+ throw new Error(
305
+ 'The `style` prop expects a mapping from style properties to values, ' +
306
+ "not a string. For example, style={{marginRight: spacing + 'em'}} when " +
307
+ 'using JSX.',
308
+ );
309
+ }
310
+ if (__DEV__) {
311
+ if (nextProp) {
312
+ // Freeze the next style object so that we can assume it won't be
313
+ // mutated. We have already warned for this in the past.
314
+ Object.freeze(nextProp);
315
+ }
316
}
317
+ // Relies on `updateStylesByID` not mutating `styleUpdates`.
318
+ setValueForStyles(domElement, nextProp);
319
+ break;
320
}
308
- // Relies on `updateStylesByID` not mutating `styleUpdates`.
309
- setValueForStyles(domElement, nextProp);
310
- } else if (propKey === DANGEROUSLY_SET_INNER_HTML) {
311
- const nextHtml = nextProp ? nextProp[HTML] : undefined;
312
- if (nextHtml != null) {
313
- if (disableIEWorkarounds) {
314
- domElement.innerHTML = nextHtml;
315
- } else {
316
- setInnerHTML(domElement, nextHtml);
321
+ case 'dangerouslySetInnerHTML': {
322
+ if (nextProp != null) {
323
+ if (typeof nextProp !== 'object' || !('__html' in nextProp)) {
324
+ throw new Error(
325
+ '`props.dangerouslySetInnerHTML` must be in the form `{__html: ...}`. ' +
326
+ 'Please visit https://reactjs.org/link/dangerously-set-inner-html ' +
327
+ 'for more information.',
328
+ );
329
+ }
330
+ const nextHtml = nextProp.__html;
331
+ if (nextHtml != null) {
332
+ if (nextProps.children != null) {
333
+ throw new Error(
334
+ 'Can only set one of `children` or `props.dangerouslySetInnerHTML`.',
335
+ );
336
+ }
337
+ if (disableIEWorkarounds) {
338
+ domElement.innerHTML = nextHtml;
339
+ } else {
340
+ setInnerHTML(domElement, nextHtml);
341
+ }
342
+ }
343
}
344
+ break;
345
}
319
- } else if (propKey === CHILDREN) {
320
- if (typeof nextProp === 'string') {
321
- // Avoid setting initial textContent when the text is empty. In IE11 setting
322
- // textContent on a <textarea> will cause the placeholder to not
323
- // show within the <textarea> until it has been focused and blurred again.
324
- // https://github.com/facebook/react/issues/6731#issuecomment-254874553
325
- const canSetTextContent =
326
- (!enableHostSingletons || tag !== 'body') &&
327
- (tag !== 'textarea' || nextProp !== '');
328
- if (canSetTextContent) {
329
- setTextContent(domElement, nextProp);
346
+ case 'children': {
347
+ if (typeof nextProp === 'string') {
348
+ // Avoid setting initial textContent when the text is empty. In IE11 setting
349
+ // textContent on a <textarea> will cause the placeholder to not
350
+ // show within the <textarea> until it has been focused and blurred again.
351
+ // https://github.com/facebook/react/issues/6731#issuecomment-254874553
352
+ const canSetTextContent =
353
+ (!enableHostSingletons || tag !== 'body') &&
354
+ (tag !== 'textarea' || nextProp !== '');
355
+ if (canSetTextContent) {
356
+ setTextContent(domElement, nextProp);
357
+ }
358
+ } else if (typeof nextProp === 'number') {
359
+ const canSetTextContent = !enableHostSingletons || tag !== 'body';
360
+ if (canSetTextContent) {
361
+ setTextContent(domElement, '' + nextProp);
362
+ }
363
}
331
- } else if (typeof nextProp === 'number') {
332
- const canSetTextContent = !enableHostSingletons || tag !== 'body';
333
- if (canSetTextContent) {
334
- setTextContent(domElement, '' + nextProp);
364
+ break;
365
+ }
366
+ case 'onScroll': {
367
+ if (nextProp != null) {
368
+ if (__DEV__ && typeof nextProp !== 'function') {
369
+ warnForInvalidEventListener(propKey, nextProp);
370
+ }
371
+ listenToNonDelegatedEvent('scroll', domElement);
372
}
373
+ break;
374
}
337
- } else if (
338
- propKey === SUPPRESS_CONTENT_EDITABLE_WARNING ||
339
- propKey === SUPPRESS_HYDRATION_WARNING
340
- ) {
341
- // Noop
342
- } else if (propKey === AUTOFOCUS) {
343
- // We polyfill it separately on the client during commit.
344
- // We could have excluded it in the property list instead of
345
- // adding a special case here, but then it wouldn't be emitted
346
- // on server rendering (but we *do* want to emit it in SSR).
347
- } else if (registrationNameDependencies.hasOwnProperty(propKey)) {
348
- if (nextProp != null) {
349
- if (__DEV__ && typeof nextProp !== 'function') {
350
- warnForInvalidEventListener(propKey, nextProp);
375
+ case 'suppressContentEditableWarning':
376
+ case 'suppressHydrationWarning':
377
+ case 'defaultValue': // Reserved
378
+ case 'defaultChecked':
379
+ case 'innerHTML': {
380
+ // Noop
381
+ break;
382
+ }
383
+ case 'autoFocus': {
384
+ // We polyfill it separately on the client during commit.
385
+ // We could have excluded it in the property list instead of
386
+ // adding a special case here, but then it wouldn't be emitted
387
+ // on server rendering (but we *do* want to emit it in SSR).
388
+ break;
389
+ }
390
+ case 'innerText': // Properties
391
+ case 'textContent':
392
+ if (enableCustomElementPropertySupport) {
393
+ break;
394
}
352
- if (propKey === 'onScroll') {
353
- listenToNonDelegatedEvent('scroll', domElement);
395
+ // eslint-disable-next-line no-fallthrough
396
+ default: {
397
+ if (registrationNameDependencies.hasOwnProperty(propKey)) {
398
+ if (nextProp != null) {
399
+ if (__DEV__ && typeof nextProp !== 'function') {
400
+ warnForInvalidEventListener(propKey, nextProp);
401
+ }
402
+ }
403
+ } else if (nextProp != null) {
404
+ if (isCustomComponentTag) {
405
+ setValueForPropertyOnCustomComponent(domElement, propKey, nextProp);
406
+ } else {
407
+ setValueForProperty(domElement, propKey, nextProp);
408
+ }
409
}
410
}
356
- } else if (nextProp != null) {
357
- setValueForProperty(domElement, propKey, nextProp, isCustomComponentTag);
411
}
412
}
413
}
422
for (let i = 0; i < updatePayload.length; i += 2) {
423
const propKey = updatePayload[i];
424
const propValue = updatePayload[i + 1];
372
- if (propKey === STYLE) {
373
- setValueForStyles(domElement, propValue);
374
- } else if (propKey === DANGEROUSLY_SET_INNER_HTML) {
375
- if (disableIEWorkarounds) {
376
- domElement.innerHTML = propValue;
377
- } else {
378
- setInnerHTML(domElement, propValue);
379
- }
380
- } else if (propKey === CHILDREN) {
381
- setTextContent(domElement, propValue);
382
- } else {
383
- setValueForProperty(domElement, propKey, propValue, isCustomComponentTag);
425
+ switch (propKey) {
426
+ case 'style':
427
+ setValueForStyles(domElement, propValue);
428
+ break;
429
+ case 'dangerouslySetInnerHTML':
430
+ if (disableIEWorkarounds) {
431
+ domElement.innerHTML = propValue;
432
+ } else {
433
+ setInnerHTML(domElement, propValue);
434
+ }
435
+ break;
436
+ case 'children':
437
+ setTextContent(domElement, propValue);
438
+ break;
439
+ default:
440
+ if (isCustomComponentTag) {
441
+ setValueForPropertyOnCustomComponent(domElement, propKey, propValue);
442
+ } else {
443
+ setValueForProperty(domElement, propKey, propValue);
444
+ }
445
+ break;
446
}
447
}
448
}
598
case 'iframe':
599
case 'object':
600
case 'embed':
601
+ if (
602
+ rawProps.children != null ||
603
+ rawProps.dangerouslySetInnerHTML != null
604
+ ) {
605
+ // TODO: Can we make this a DEV warning to avoid this deny list?
606
+ throw new Error(
607
+ `${tag} is a void element tag and must neither have \`children\` nor ` +
608
+ 'use `dangerouslySetInnerHTML`.',
609
+ );
610
+ }
611
// We listen to this event in case to ensure emulated bubble
612
// listeners still fire for the load event.
613
listenToNonDelegatedEvent('load', domElement);
623
props = rawProps;
624
break;
625
case 'source':
626
+ if (
627
+ rawProps.children != null ||
628
+ rawProps.dangerouslySetInnerHTML != null
629
+ ) {
630
+ // TODO: Can we make this a DEV warning to avoid this deny list?
631
+ throw new Error(
632
+ `${tag} is a void element tag and must neither have \`children\` nor ` +
633
+ 'use `dangerouslySetInnerHTML`.',
634
+ );
635
+ }
636
// We listen to this event in case to ensure emulated bubble
637
// listeners still fire for the error event.
638
listenToNonDelegatedEvent('error', domElement);
639
props = rawProps;
640
break;
641
case 'img':
560
- case 'image':
642
case 'link':
643
+ if (
644
+ rawProps.children != null ||
645
+ rawProps.dangerouslySetInnerHTML != null
646
+ ) {
647
+ throw new Error(
648
+ `${tag} is a void element tag and must neither have \`children\` nor ` +
649
+ 'use `dangerouslySetInnerHTML`.',
650
+ );
651
+ }
652
+ // eslint-disable-next-line no-fallthrough
653
+ case 'image':
654
// We listen to these events in case to ensure emulated bubble
655
// listeners still fire for error and load events.
656
listenToNonDelegatedEvent('error', domElement);
664
props = rawProps;
665
break;
666
case 'input':
667
+ if (
668
+ rawProps.children != null ||
669
+ rawProps.dangerouslySetInnerHTML != null
670
+ ) {
671
+ throw new Error(
672
+ `${tag} is a void element tag and must neither have \`children\` nor ` +
673
+ 'use `dangerouslySetInnerHTML`.',
674
+ );
675
+ }
676
ReactDOMInputInitWrapperState(domElement, rawProps);
677
props = ReactDOMInputGetHostProps(domElement, rawProps);
678
// We listen to this event in case to ensure emulated bubble
697
// listeners still fire for the invalid event.
698
listenToNonDelegatedEvent('invalid', domElement);
699
break;
700
+ case 'area':
701
+ case 'base':
702
+ case 'br':
703
+ case 'col':
704
+ case 'hr':
705
+ case 'keygen':
706
+ case 'meta':
707
+ case 'param':
708
+ case 'track':
709
+ case 'wbr':
710
+ case 'menuitem': {
711
+ if (
712
+ rawProps.children != null ||
713
+ rawProps.dangerouslySetInnerHTML != null
714
+ ) {
715
+ // TODO: Can we make this a DEV warning to avoid this deny list?
716
+ throw new Error(
717
+ `${tag} is a void element tag and must neither have \`children\` nor ` +
718
+ 'use `dangerouslySetInnerHTML`.',
719
+ );
720
+ }
721
+ }
722
+ // eslint-disable-next-line no-fallthrough
723
default:
724
props = rawProps;
725
}
726
603
- assertValidProps(tag, props);
604
-
727
setInitialDOMProperties(tag, domElement, props, isCustomComponentTag);
728
729
switch (tag) {
771
let nextProps: Object;
772
switch (tag) {
773
case 'input':
774
+ if (
775
+ nextRawProps.children != null ||
776
+ nextRawProps.dangerouslySetInnerHTML != null
777
+ ) {
778
+ throw new Error(
779
+ `${tag} is a void element tag and must neither have \`children\` nor ` +
780
+ 'use `dangerouslySetInnerHTML`.',
781
+ );
782
+ }
783
lastProps = ReactDOMInputGetHostProps(domElement, lastRawProps);
784
nextProps = ReactDOMInputGetHostProps(domElement, nextRawProps);
785
updatePayload = [];
794
nextProps = ReactDOMTextareaGetHostProps(domElement, nextRawProps);
795
updatePayload = [];
796
break;
797
+ case 'img':
798
+ case 'link':
799
+ case 'area':
800
+ case 'base':
801
+ case 'br':
802
+ case 'col':
803
+ case 'embed':
804
+ case 'hr':
805
+ case 'keygen':
806
+ case 'meta':
807
+ case 'param':
808
+ case 'source':
809
+ case 'track':
810
+ case 'wbr':
811
+ case 'menuitem': {
812
+ if (
813
+ nextRawProps.children != null ||
814
+ nextRawProps.dangerouslySetInnerHTML != null
815
+ ) {
816
+ // TODO: Can we make this a DEV warning to avoid this deny list?
817
+ throw new Error(
818
+ `${tag} is a void element tag and must neither have \`children\` nor ` +
819
+ 'use `dangerouslySetInnerHTML`.',
820
+ );
821
+ }
822
+ }
823
+ // eslint-disable-next-line no-fallthrough
824
default:
825
lastProps = lastRawProps;
826
nextProps = nextRawProps;
834
break;
835
}
836
679
- assertValidProps(tag, nextProps);
680
-
837
let propKey;
838
let styleName;
839
let styleUpdates = null;
845
) {
846
continue;
847
}
692
- if (propKey === STYLE) {
693
- const lastStyle = lastProps[propKey];
694
- for (styleName in lastStyle) {
695
- if (lastStyle.hasOwnProperty(styleName)) {
696
- if (!styleUpdates) {
697
- styleUpdates = ({}: {[string]: $FlowFixMe});
848
+ switch (propKey) {
849
+ case 'style': {
850
+ const lastStyle = lastProps[propKey];
851
+ for (styleName in lastStyle) {
852
+ if (lastStyle.hasOwnProperty(styleName)) {
853
+ if (!styleUpdates) {
854
+ styleUpdates = ({}: {[string]: $FlowFixMe});
855
+ }
856
+ styleUpdates[styleName] = '';
857
}
699
- styleUpdates[styleName] = '';
858
}
859
+ break;
860
}
702
- } else if (propKey === DANGEROUSLY_SET_INNER_HTML || propKey === CHILDREN) {
703
- // Noop. This is handled by the clear text mechanism.
704
- } else if (
705
- propKey === SUPPRESS_CONTENT_EDITABLE_WARNING ||
706
- propKey === SUPPRESS_HYDRATION_WARNING
707
- ) {
708
- // Noop
709
- } else if (propKey === AUTOFOCUS) {
710
- // Noop. It doesn't work on updates anyway.
711
- } else if (registrationNameDependencies.hasOwnProperty(propKey)) {
712
- // This is a special case. If any listener updates we need to ensure
713
- // that the "current" fiber pointer gets updated so we need a commit
714
- // to update this element.
715
- if (!updatePayload) {
716
- updatePayload = [];
861
+ case 'dangerouslySetInnerHTML':
862
+ case 'children': {
863
+ // Noop. This is handled by the clear text mechanism.
864
+ break;
865
+ }
866
+ case 'suppressContentEditableWarning':
867
+ case 'suppressHydrationWarning':
868
+ case 'defaultValue': // Reserved
869
+ case 'defaultChecked':
870
+ case 'innerHTML': {
871
+ // Noop
872
+ break;
873
+ }
874
+ case 'autoFocus': {
875
+ // Noop. It doesn't work on updates anyway.
876
+ break;
877
+ }
878
+ case 'innerText': // Properties
879
+ case 'textContent':
880
+ if (enableCustomElementPropertySupport) {
881
+ break;
882
+ }
883
+ // eslint-disable-next-line no-fallthrough
884
+ default: {
885
+ if (registrationNameDependencies.hasOwnProperty(propKey)) {
886
+ // This is a special case. If any listener updates we need to ensure
887
+ // that the "current" fiber pointer gets updated so we need a commit
888
+ // to update this element.
889
+ if (!updatePayload) {
890
+ updatePayload = [];
891
+ }
892
+ } else {
893
+ // For all other deleted properties we add it to the queue. We use
894
+ // the allowed property list in the commit phase instead.
895
+ (updatePayload = updatePayload || []).push(propKey, null);
896
+ }
897
}
718
- } else {
719
- // For all other deleted properties we add it to the queue. We use
720
- // the allowed property list in the commit phase instead.
721
- (updatePayload = updatePayload || []).push(propKey, null);
898
}
899
}
900
for (propKey in nextProps) {
907
) {
908
continue;
909
}
734
- if (propKey === STYLE) {
735
- if (__DEV__) {
736
- if (nextProp) {
737
- // Freeze the next style object so that we can assume it won't be
738
- // mutated. We have already warned for this in the past.
739
- Object.freeze(nextProp);
910
+ switch (propKey) {
911
+ case 'style': {
912
+ if (nextProp != null && typeof nextProp !== 'object') {
913
+ throw new Error(
914
+ 'The `style` prop expects a mapping from style properties to values, ' +
915
+ "not a string. For example, style={{marginRight: spacing + 'em'}} when " +
916
+ 'using JSX.',
917
+ );
918
}
741
- }
742
- if (lastProp) {
743
- // Unset styles on `lastProp` but not on `nextProp`.
744
- for (styleName in lastProp) {
745
- if (
746
- lastProp.hasOwnProperty(styleName) &&
747
- (!nextProp || !nextProp.hasOwnProperty(styleName))
748
- ) {
749
- if (!styleUpdates) {
750
- styleUpdates = ({}: {[string]: string});
751
- }
752
- styleUpdates[styleName] = '';
919
+ if (__DEV__) {
920
+ if (nextProp) {
921
+ // Freeze the next style object so that we can assume it won't be
922
+ // mutated. We have already warned for this in the past.
923
+ Object.freeze(nextProp);
924
}
925
}
755
- // Update styles that changed since `lastProp`.
756
- for (styleName in nextProp) {
757
- if (
758
- nextProp.hasOwnProperty(styleName) &&
759
- lastProp[styleName] !== nextProp[styleName]
760
- ) {
761
- if (!styleUpdates) {
762
- styleUpdates = ({}: {[string]: $FlowFixMe});
926
+ if (lastProp) {
927
+ // Unset styles on `lastProp` but not on `nextProp`.
928
+ for (styleName in lastProp) {
929
+ if (
930
+ lastProp.hasOwnProperty(styleName) &&
931
+ (!nextProp || !nextProp.hasOwnProperty(styleName))
932
+ ) {
933
+ if (!styleUpdates) {
934
+ styleUpdates = ({}: {[string]: string});
935
+ }
936
+ styleUpdates[styleName] = '';
937
}
764
- styleUpdates[styleName] = nextProp[styleName];
938
}
766
- }
767
- } else {
768
- // Relies on `updateStylesByID` not mutating `styleUpdates`.
769
- if (!styleUpdates) {
770
- if (!updatePayload) {
771
- updatePayload = [];
939
+ // Update styles that changed since `lastProp`.
940
+ for (styleName in nextProp) {
941
+ if (
942
+ nextProp.hasOwnProperty(styleName) &&
943
+ lastProp[styleName] !== nextProp[styleName]
944
+ ) {
945
+ if (!styleUpdates) {
946
+ styleUpdates = ({}: {[string]: $FlowFixMe});
947
+ }
948
+ styleUpdates[styleName] = nextProp[styleName];
949
+ }
950
}
773
- updatePayload.push(propKey, styleUpdates);
951
+ } else {
952
+ // Relies on `updateStylesByID` not mutating `styleUpdates`.
953
+ if (!styleUpdates) {
954
+ if (!updatePayload) {
955
+ updatePayload = [];
956
+ }
957
+ updatePayload.push(propKey, styleUpdates);
958
+ }
959
+ styleUpdates = nextProp;
960
}
775
- styleUpdates = nextProp;
961
+ break;
962
}
777
- } else if (propKey === DANGEROUSLY_SET_INNER_HTML) {
778
- const nextHtml = nextProp ? nextProp[HTML] : undefined;
779
- const lastHtml = lastProp ? lastProp[HTML] : undefined;
780
- if (nextHtml != null) {
781
- if (lastHtml !== nextHtml) {
782
- (updatePayload = updatePayload || []).push(propKey, nextHtml);
963
+ case 'dangerouslySetInnerHTML': {
964
+ if (nextProp != null) {
965
+ if (typeof nextProp !== 'object' || !('__html' in nextProp)) {
966
+ throw new Error(
967
+ '`props.dangerouslySetInnerHTML` must be in the form `{__html: ...}`. ' +
968
+ 'Please visit https://reactjs.org/link/dangerously-set-inner-html ' +
969
+ 'for more information.',
970
+ );
971
+ }
972
+ const nextHtml = nextProp.__html;
973
+ if (nextHtml != null) {
974
+ if (nextProps.children != null) {
975
+ throw new Error(
976
+ 'Can only set one of `children` or `props.dangerouslySetInnerHTML`.',
977
+ );
978
+ }
979
+ const lastHtml = lastProp ? lastProp.__html : undefined;
980
+ if (lastHtml !== nextHtml) {
981
+ (updatePayload = updatePayload || []).push(propKey, nextHtml);
982
+ }
983
+ }
984
+ } else {
985
+ // TODO: It might be too late to clear this if we have children
986
+ // inserted already.
987
}
784
- } else {
785
- // TODO: It might be too late to clear this if we have children
786
- // inserted already.
787
- }
788
- } else if (propKey === CHILDREN) {
789
- if (typeof nextProp === 'string' || typeof nextProp === 'number') {
790
- (updatePayload = updatePayload || []).push(propKey, '' + nextProp);
988
+ break;
989
}
792
- } else if (
793
- propKey === SUPPRESS_CONTENT_EDITABLE_WARNING ||
794
- propKey === SUPPRESS_HYDRATION_WARNING
795
- ) {
796
- // Noop
797
- } else if (registrationNameDependencies.hasOwnProperty(propKey)) {
798
- if (nextProp != null) {
799
- // We eagerly listen to this even though we haven't committed yet.
800
- if (__DEV__ && typeof nextProp !== 'function') {
801
- warnForInvalidEventListener(propKey, nextProp);
990
+ case 'children': {
991
+ if (typeof nextProp === 'string' || typeof nextProp === 'number') {
992
+ (updatePayload = updatePayload || []).push(propKey, '' + nextProp);
993
}
803
- if (propKey === 'onScroll') {
994
+ break;
995
+ }
996
+ case 'onScroll': {
997
+ if (nextProp != null) {
998
+ // We eagerly listen to this even though we haven't committed yet.
999
+ if (__DEV__ && typeof nextProp !== 'function') {
1000
+ warnForInvalidEventListener(propKey, nextProp);
1001
+ }
1002
listenToNonDelegatedEvent('scroll', domElement);
1003
}
1004
+ if (!updatePayload && lastProp !== nextProp) {
1005
+ // This is a special case. If any listener updates we need to ensure
1006
+ // that the "current" props pointer gets updated so we need a commit
1007
+ // to update this element.
1008
+ updatePayload = [];
1009
+ }
1010
+ break;
1011
}
807
- if (!updatePayload && lastProp !== nextProp) {
808
- // This is a special case. If any listener updates we need to ensure
809
- // that the "current" props pointer gets updated so we need a commit
810
- // to update this element.
811
- updatePayload = [];
1012
+ case 'suppressContentEditableWarning':
1013
+ case 'suppressHydrationWarning':
1014
+ case 'defaultValue': // Reserved
1015
+ case 'defaultChecked':
1016
+ case 'innerHTML': {
1017
+ // Noop
1018
+ break;
1019
+ }
1020
+ case 'autoFocus': {
1021
+ // Noop on updates
1022
+ break;
1023
+ }
1024
+ case 'innerText': // Properties
1025
+ case 'textContent':
1026
+ if (enableCustomElementPropertySupport) {
1027
+ break;
1028
+ }
1029
+ // eslint-disable-next-line no-fallthrough
1030
+ default: {
1031
+ if (registrationNameDependencies.hasOwnProperty(propKey)) {
1032
+ if (nextProp != null) {
1033
+ // We eagerly listen to this even though we haven't committed yet.
1034
+ if (__DEV__ && typeof nextProp !== 'function') {
1035
+ warnForInvalidEventListener(propKey, nextProp);
1036
+ }
1037
+ }
1038
+ if (!updatePayload && lastProp !== nextProp) {
1039
+ // This is a special case. If any listener updates we need to ensure
1040
+ // that the "current" props pointer gets updated so we need a commit
1041
+ // to update this element.
1042
+ updatePayload = [];
1043
+ }
1044
+ } else {
1045
+ // For any other property we always add it to the queue and then we
1046
+ // filter it out using the allowed property list during the commit.
1047
+ (updatePayload = updatePayload || []).push(propKey, nextProp);
1048
+ }
1049
}
813
- } else {
814
- // For any other property we always add it to the queue and then we
815
- // filter it out using the allowed property list during the commit.
816
- (updatePayload = updatePayload || []).push(propKey, nextProp);
1050
}
1051
}
1052
if (styleUpdates) {
1053
if (__DEV__) {
821
- validateShorthandPropertyCollisionInDev(styleUpdates, nextProps[STYLE]);
1054
+ validateShorthandPropertyCollisionInDev(styleUpdates, nextProps.style);
1055
}
823
- (updatePayload = updatePayload || []).push(STYLE, styleUpdates);
1056
+ (updatePayload = updatePayload || []).push('style', styleUpdates);
1057
}
1058
return updatePayload;
1059
}
1118
return null;
1119
}
1120
1121
+function diffHydratedStyles(domElement: Element, value: mixed) {
1122
+ if (value != null && typeof value !== 'object') {
1123
+ throw new Error(
1124
+ 'The `style` prop expects a mapping from style properties to values, ' +
1125
+ "not a string. For example, style={{marginRight: spacing + 'em'}} when " +
1126
+ 'using JSX.',
1127
+ );
1128
+ }
1129
+ if (canDiffStyleForHydrationWarning) {
1130
+ const expectedStyle = createDangerousStringForStyles(value);
1131
+ const serverValue = domElement.getAttribute('style');
1132
+ if (expectedStyle !== serverValue) {
1133
+ warnForPropDifference('style', serverValue, expectedStyle);
1134
+ }
1135
+ }
1136
+}
1137
+
1138
+function diffHydratedCustomComponent(
1139
+ domElement: Element,
1140
+ tag: string,
1141
+ rawProps: Object,
1142
+ parentNamespaceDev: string,
1143
+ extraAttributeNames: Set<string>,
1144
+) {
1145
+ for (const propKey in rawProps) {
1146
+ if (!rawProps.hasOwnProperty(propKey)) {
1147
+ continue;
1148
+ }
1149
+ const nextProp = rawProps[propKey];
1150
+ if (nextProp == null) {
1151
+ continue;
1152
+ }
1153
+ if (registrationNameDependencies.hasOwnProperty(propKey)) {
1154
+ if (typeof nextProp !== 'function') {
1155
+ warnForInvalidEventListener(propKey, nextProp);
1156
+ }
1157
+ continue;
1158
+ }
1159
+ if (rawProps.suppressHydrationWarning === true) {
1160
+ // Don't bother comparing. We're ignoring all these warnings.
1161
+ continue;
1162
+ }
1163
+ // Validate that the properties correspond to their expected values.
1164
+ switch (propKey) {
1165
+ case 'children': // Checked above already
1166
+ case 'suppressContentEditableWarning':
1167
+ case 'suppressHydrationWarning':
1168
+ case 'defaultValue':
1169
+ case 'defaultChecked':
1170
+ case 'innerHTML':
1171
+ // Noop
1172
+ continue;
1173
+ case 'dangerouslySetInnerHTML':
1174
+ const serverHTML = domElement.innerHTML;
1175
+ const nextHtml = nextProp ? nextProp.__html : undefined;
1176
+ if (nextHtml != null) {
1177
+ const expectedHTML = normalizeHTML(domElement, nextHtml);
1178
+ if (expectedHTML !== serverHTML) {
1179
+ warnForPropDifference(propKey, serverHTML, expectedHTML);
1180
+ }
1181
+ }
1182
+ continue;
1183
+ case 'style':
1184
+ // $FlowFixMe - Should be inferred as not undefined.
1185
+ extraAttributeNames.delete(propKey);
1186
+ diffHydratedStyles(domElement, nextProp);
1187
+ continue;
1188
+ case 'offsetParent':
1189
+ case 'offsetTop':
1190
+ case 'offsetLeft':
1191
+ case 'offsetWidth':
1192
+ case 'offsetHeight':
1193
+ case 'isContentEditable':
1194
+ case 'outerText':
1195
+ case 'outerHTML':
1196
+ if (enableCustomElementPropertySupport) {
1197
+ // $FlowFixMe - Should be inferred as not undefined.
1198
+ extraAttributeNames.delete(propKey.toLowerCase());
1199
+ if (__DEV__) {
1200
+ console.error(
1201
+ 'Assignment to read-only property will result in a no-op: `%s`',
1202
+ propKey,
1203
+ );
1204
+ }
1205
+ continue;
1206
+ }
1207
+ // eslint-disable-next-line no-fallthrough
1208
+ default: {
1209
+ let ownNamespaceDev = parentNamespaceDev;
1210
+ if (ownNamespaceDev === HTML_NAMESPACE) {
1211
+ ownNamespaceDev = getIntrinsicNamespace(tag);
1212
+ }
1213
+ if (ownNamespaceDev === HTML_NAMESPACE) {
1214
+ // $FlowFixMe - Should be inferred as not undefined.
1215
+ extraAttributeNames.delete(propKey.toLowerCase());
1216
+ } else {
1217
+ // $FlowFixMe - Should be inferred as not undefined.
1218
+ extraAttributeNames.delete(propKey);
1219
+ }
1220
+ const serverValue = getValueForAttributeOnCustomComponent(
1221
+ domElement,
1222
+ propKey,
1223
+ nextProp,
1224
+ );
1225
+ if (nextProp !== serverValue) {
1226
+ warnForPropDifference(propKey, serverValue, nextProp);
1227
+ }
1228
+ }
1229
+ }
1230
+ }
1231
+}
1232
+
1233
+function diffHydratedGenericElement(
1234
+ domElement: Element,
1235
+ tag: string,
1236
+ rawProps: Object,
1237
+ parentNamespaceDev: string,
1238
+ extraAttributeNames: Set<string>,
1239
+) {
1240
+ for (const propKey in rawProps) {
1241
+ if (!rawProps.hasOwnProperty(propKey)) {
1242
+ continue;
1243
+ }
1244
+ const nextProp = rawProps[propKey];
1245
+ if (nextProp == null) {
1246
+ continue;
1247
+ }
1248
+ if (registrationNameDependencies.hasOwnProperty(propKey)) {
1249
+ if (typeof nextProp !== 'function') {
1250
+ warnForInvalidEventListener(propKey, nextProp);
1251
+ }
1252
+ continue;
1253
+ }
1254
+ if (rawProps.suppressHydrationWarning === true) {
1255
+ // Don't bother comparing. We're ignoring all these warnings.
1256
+ continue;
1257
+ }
1258
+ // Validate that the properties correspond to their expected values.
1259
+ switch (propKey) {
1260
+ case 'children': // Checked above already
1261
+ case 'suppressContentEditableWarning':
1262
+ case 'suppressHydrationWarning':
1263
+ case 'value': // Controlled attributes are not validated
1264
+ case 'checked': // TODO: Only ignore them on controlled tags.
1265
+ case 'selected':
1266
+ case 'defaultValue':
1267
+ case 'defaultChecked':
1268
+ case 'innerHTML':
1269
+ // Noop
1270
+ continue;
1271
+ case 'dangerouslySetInnerHTML':
1272
+ const serverHTML = domElement.innerHTML;
1273
+ const nextHtml = nextProp ? nextProp.__html : undefined;
1274
+ if (nextHtml != null) {
1275
+ const expectedHTML = normalizeHTML(domElement, nextHtml);
1276
+ if (expectedHTML !== serverHTML) {
1277
+ warnForPropDifference(propKey, serverHTML, expectedHTML);
1278
+ }
1279
+ }
1280
+ continue;
1281
+ case 'style':
1282
+ // $FlowFixMe - Should be inferred as not undefined.
1283
+ extraAttributeNames.delete(propKey);
1284
+ diffHydratedStyles(domElement, nextProp);
1285
+ continue;
1286
+ // eslint-disable-next-line no-fallthrough
1287
+ default:
1288
+ if (
1289
+ // shouldIgnoreAttribute
1290
+ // We have already filtered out null/undefined and reserved words.
1291
+ propKey.length > 2 &&
1292
+ (propKey[0] === 'o' || propKey[0] === 'O') &&
1293
+ (propKey[1] === 'n' || propKey[1] === 'N')
1294
+ ) {
1295
+ continue;
1296
+ }
1297
+ const propertyInfo = getPropertyInfo(propKey);
1298
+ let isMismatchDueToBadCasing = false;
1299
+ let serverValue;
1300
+ if (propertyInfo !== null) {
1301
+ // $FlowFixMe - Should be inferred as not undefined.
1302
+ extraAttributeNames.delete(propertyInfo.attributeName);
1303
+ serverValue = getValueForProperty(
1304
+ domElement,
1305
+ propKey,
1306
+ nextProp,
1307
+ propertyInfo,
1308
+ );
1309
+ } else {
1310
+ let ownNamespaceDev = parentNamespaceDev;
1311
+ if (ownNamespaceDev === HTML_NAMESPACE) {
1312
+ ownNamespaceDev = getIntrinsicNamespace(tag);
1313
+ }
1314
+ if (ownNamespaceDev === HTML_NAMESPACE) {
1315
+ // $FlowFixMe - Should be inferred as not undefined.
1316
+ extraAttributeNames.delete(propKey.toLowerCase());
1317
+ } else {
1318
+ const standardName = getPossibleStandardName(propKey);
1319
+ if (standardName !== null && standardName !== propKey) {
1320
+ // If an SVG prop is supplied with bad casing, it will
1321
+ // be successfully parsed from HTML, but will produce a mismatch
1322
+ // (and would be incorrectly rendered on the client).
1323
+ // However, we already warn about bad casing elsewhere.
1324
+ // So we'll skip the misleading extra mismatch warning in this case.
1325
+ isMismatchDueToBadCasing = true;
1326
+ // $FlowFixMe - Should be inferred as not undefined.
1327
+ extraAttributeNames.delete(standardName);
1328
+ }
1329
+ // $FlowFixMe - Should be inferred as not undefined.
1330
+ extraAttributeNames.delete(propKey);
1331
+ }
1332
+ serverValue = getValueForAttribute(domElement, propKey, nextProp);
1333
+ }
1334
+
1335
+ if (nextProp !== serverValue && !isMismatchDueToBadCasing) {
1336
+ warnForPropDifference(propKey, serverValue, nextProp);
1337
+ }
1338
+ }
1339
+ }
1340
+}
1341
+
1342
export function diffHydratedProperties(
1343
domElement: Element,
1344
tag: string,
1347
shouldWarnDev: boolean,
1348
parentNamespaceDev: string,
1349
): null | Array<mixed> {
896
- let extraAttributeNames: Set<string>;
897
-
1350
if (__DEV__) {
1351
validatePropertiesInDevelopment(tag, rawProps);
1352
}
1417
listenToNonDelegatedEvent('scroll', domElement);
1418
}
1419
968
- assertValidProps(tag, rawProps);
969
-
970
- if (__DEV__) {
971
- extraAttributeNames = new Set();
972
- const attributes = domElement.attributes;
973
- for (let i = 0; i < attributes.length; i++) {
974
- const name = attributes[i].name.toLowerCase();
975
- switch (name) {
976
- // Controlled attributes are not validated
977
- // TODO: Only ignore them on controlled tags.
978
- case 'value':
979
- break;
980
- case 'checked':
981
- break;
982
- case 'selected':
983
- break;
984
- default:
985
- // Intentionally use the original name.
986
- // See discussion in https://github.com/facebook/react/pull/10676.
987
- extraAttributeNames.add(attributes[i].name);
988
- }
989
- }
990
- }
991
-
1420
let updatePayload = null;
1421
1422
const children = rawProps.children;
1431
// TODO: Should we use domElement.firstChild.nodeValue to compare?
1432
if (typeof children === 'string' || typeof children === 'number') {
1433
if (domElement.textContent !== '' + children) {
1006
- if (rawProps[SUPPRESS_HYDRATION_WARNING] !== true) {
1434
+ if (rawProps.suppressHydrationWarning !== true) {
1435
checkForUnmatchedText(
1436
domElement.textContent,
1437
children,
1440
);
1441
}
1442
if (!isConcurrentMode) {
1015
- updatePayload = [CHILDREN, children];
1443
+ updatePayload = ['children', children];
1444
}
1445
}
1446
}
1447
1448
if (__DEV__ && shouldWarnDev) {
1021
- const isCustomComponentTag = isCustomComponent(tag, rawProps);
1022
-
1023
- for (const propKey in rawProps) {
1024
- if (!rawProps.hasOwnProperty(propKey)) {
1025
- continue;
1026
- }
1027
- const nextProp = rawProps[propKey];
1028
- if (propKey === CHILDREN) {
1029
- // Checked above already
1030
- } else if (registrationNameDependencies.hasOwnProperty(propKey)) {
1031
- if (nextProp != null) {
1032
- if (typeof nextProp !== 'function') {
1033
- warnForInvalidEventListener(propKey, nextProp);
1034
- }
1035
- }
1036
- } else {
1037
- // Validate that the properties correspond to their expected values.
1038
- let serverValue;
1039
- const propertyInfo =
1040
- isCustomComponentTag && enableCustomElementPropertySupport
1041
- ? null
1042
- : getPropertyInfo(propKey);
1043
- if (rawProps[SUPPRESS_HYDRATION_WARNING] === true) {
1044
- // Don't bother comparing. We're ignoring all these warnings.
1045
- } else if (
1046
- propKey === SUPPRESS_CONTENT_EDITABLE_WARNING ||
1047
- propKey === SUPPRESS_HYDRATION_WARNING ||
1048
- // Controlled attributes are not validated
1049
- // TODO: Only ignore them on controlled tags.
1050
- propKey === 'value' ||
1051
- propKey === 'checked' ||
1052
- propKey === 'selected'
1053
- ) {
1054
- // Noop
1055
- } else if (propKey === DANGEROUSLY_SET_INNER_HTML) {
1056
- const serverHTML = domElement.innerHTML;
1057
- const nextHtml = nextProp ? nextProp[HTML] : undefined;
1058
- if (nextHtml != null) {
1059
- const expectedHTML = normalizeHTML(domElement, nextHtml);
1060
- if (expectedHTML !== serverHTML) {
1061
- warnForPropDifference(propKey, serverHTML, expectedHTML);
1062
- }
1063
- }
1064
- } else if (propKey === STYLE) {
1065
- // $FlowFixMe - Should be inferred as not undefined.
1066
- extraAttributeNames.delete(propKey);
1067
-
1068
- if (canDiffStyleForHydrationWarning) {
1069
- const expectedStyle = createDangerousStringForStyles(nextProp);
1070
- serverValue = domElement.getAttribute('style');
1071
- if (expectedStyle !== serverValue) {
1072
- warnForPropDifference(propKey, serverValue, expectedStyle);
1073
- }
1074
- }
1075
- } else if (
1076
- enableCustomElementPropertySupport &&
1077
- isCustomComponentTag &&
1078
- (propKey === 'offsetParent' ||
1079
- propKey === 'offsetTop' ||
1080
- propKey === 'offsetLeft' ||
1081
- propKey === 'offsetWidth' ||
1082
- propKey === 'offsetHeight' ||
1083
- propKey === 'isContentEditable' ||
1084
- propKey === 'outerText' ||
1085
- propKey === 'outerHTML')
1086
- ) {
1087
- // $FlowFixMe - Should be inferred as not undefined.
1088
- extraAttributeNames.delete(propKey.toLowerCase());
1089
- if (__DEV__) {
1090
- console.error(
1091
- 'Assignment to read-only property will result in a no-op: `%s`',
1092
- propKey,
1093
- );
1094
- }
1095
- } else if (
1096
- isCustomComponentTag &&
1097
- !enableCustomElementPropertySupport
1098
- ) {
1099
- // $FlowFixMe - Should be inferred as not undefined.
1100
- extraAttributeNames.delete(propKey.toLowerCase());
1101
- serverValue = getValueForAttribute(
1102
- domElement,
1103
- propKey,
1104
- nextProp,
1105
- isCustomComponentTag,
1106
- );
1107
-
1108
- if (nextProp !== serverValue) {
1109
- warnForPropDifference(propKey, serverValue, nextProp);
1110
- }
1111
- } else if (
1112
- !shouldIgnoreAttribute(propKey, propertyInfo, isCustomComponentTag) &&
1113
- !shouldRemoveAttribute(
1114
- propKey,
1115
- nextProp,
1116
- propertyInfo,
1117
- isCustomComponentTag,
1118
- )
1119
- ) {
1120
- let isMismatchDueToBadCasing = false;
1121
- if (propertyInfo !== null) {
1122
- // $FlowFixMe - Should be inferred as not undefined.
1123
- extraAttributeNames.delete(propertyInfo.attributeName);
1124
- serverValue = getValueForProperty(
1125
- domElement,
1126
- propKey,
1127
- nextProp,
1128
- propertyInfo,
1129
- );
1130
- } else {
1131
- let ownNamespaceDev = parentNamespaceDev;
1132
- if (ownNamespaceDev === HTML_NAMESPACE) {
1133
- ownNamespaceDev = getIntrinsicNamespace(tag);
1134
- }
1135
- if (ownNamespaceDev === HTML_NAMESPACE) {
1136
- // $FlowFixMe - Should be inferred as not undefined.
1137
- extraAttributeNames.delete(propKey.toLowerCase());
1138
- } else {
1139
- const standardName = getPossibleStandardName(propKey);
1140
- if (standardName !== null && standardName !== propKey) {
1141
- // If an SVG prop is supplied with bad casing, it will
1142
- // be successfully parsed from HTML, but will produce a mismatch
1143
- // (and would be incorrectly rendered on the client).
1144
- // However, we already warn about bad casing elsewhere.
1145
- // So we'll skip the misleading extra mismatch warning in this case.
1146
- isMismatchDueToBadCasing = true;
1147
- // $FlowFixMe - Should be inferred as not undefined.
1148
- extraAttributeNames.delete(standardName);
1149
- }
1150
- // $FlowFixMe - Should be inferred as not undefined.
1151
- extraAttributeNames.delete(propKey);
1152
- }
1153
- serverValue = getValueForAttribute(
1154
- domElement,
1155
- propKey,
1156
- nextProp,
1157
- isCustomComponentTag,
1158
- );
1159
- }
1160
-
1161
- const dontWarnCustomElement =
1162
- enableCustomElementPropertySupport &&
1163
- isCustomComponentTag &&
1164
- (typeof nextProp === 'function' || typeof nextProp === 'object');
1165
- if (
1166
- !dontWarnCustomElement &&
1167
- nextProp !== serverValue &&
1168
- !isMismatchDueToBadCasing
1169
- ) {
1170
- warnForPropDifference(propKey, serverValue, nextProp);
1171
- }
1172
- }
1449
+ const extraAttributeNames: Set<string> = new Set();
1450
+ const attributes = domElement.attributes;
1451
+ for (let i = 0; i < attributes.length; i++) {
1452
+ const name = attributes[i].name.toLowerCase();
1453
+ switch (name) {
1454
+ // Controlled attributes are not validated
1455
+ // TODO: Only ignore them on controlled tags.
1456
+ case 'value':
1457
+ break;
1458
+ case 'checked':
1459
+ break;
1460
+ case 'selected':
1461
+ break;
1462
+ default:
1463
+ // Intentionally use the original name.
1464
+ // See discussion in https://github.com/facebook/react/pull/10676.
1465
+ extraAttributeNames.add(attributes[i].name);
1466
}
1467
}
1175
-
1468
+ if (isCustomComponent(tag, rawProps)) {
1469
+ diffHydratedCustomComponent(
1470
+ domElement,
1471
+ tag,
1472
+ rawProps,
1473
+ parentNamespaceDev,
1474
+ extraAttributeNames,
1475
+ );
1476
+ } else {
1477
+ diffHydratedGenericElement(
1478
+ domElement,
1479
+ tag,
1480
+ rawProps,
1481
+ parentNamespaceDev,
1482
+ extraAttributeNames,
1483
+ );
1484
+ }
1485
if (
1486
// $FlowFixMe - Should be inferred as not undefined.
1487
extraAttributeNames.size > 0 &&
1179
- rawProps[SUPPRESS_HYDRATION_WARNING] !== true
1488
+ rawProps.suppressHydrationWarning !== true
1489
) {
1490
// $FlowFixMe - Should be inferred as not undefined.
1491
warnForExtraAttributes(extraAttributeNames);