69
disableIEWorkarounds,
70
enableTrustedTypesIntegration,
71
enableFilterEmptyStringAttributesDOM,
72
+ diffInCommitPhase,
73
} from 'shared/ReactFeatureFlags';
74
import {
75
mediaEventTypes,
274
key: string,
275
value: mixed,
276
props: any,
277
+ prevValue: mixed,
278
): void {
279
switch (key) {
280
case 'children': {
316
break;
317
}
318
case 'style': {
317
- setValueForStyles(domElement, value);
319
+ setValueForStyles(domElement, value, prevValue);
320
break;
321
}
322
// These attributes accept URLs. These must not allow javascript: URLS.
657
);
658
break;
659
// Properties that should not be allowed on custom elements.
660
+ case 'is': {
661
+ if (__DEV__) {
662
+ if (prevValue != null) {
663
+ console.error(
664
+ 'Cannot update the "is" prop after it has been initialized.',
665
+ );
666
+ }
667
+ }
668
+ // TODO: We shouldn't actually set this attribute, because we've already
669
+ // passed it to createElement. We don't also need the attribute.
670
+ // However, our tests currently query for it so it's plausible someone
671
+ // else does too so it's break.
672
+ setValueForAttribute(domElement, 'is', value);
673
+ break;
674
+ }
675
case 'innerText':
676
case 'textContent':
677
if (enableCustomElementPropertySupport) {
706
key: string,
707
value: mixed,
708
props: any,
709
+ prevValue: mixed,
710
): void {
711
switch (key) {
712
case 'style': {
695
- setValueForStyles(domElement, value);
713
+ setValueForStyles(domElement, value, prevValue);
714
break;
715
}
716
case 'dangerouslySetInnerHTML': {
877
}
878
// defaultChecked and defaultValue are ignored by setProp
879
default: {
862
- setProp(domElement, tag, propKey, propValue, props);
880
+ setProp(domElement, tag, propKey, propValue, props, null);
881
}
882
}
883
}
910
}
911
// defaultValue are ignored by setProp
912
default: {
895
- setProp(domElement, tag, propKey, propValue, props);
913
+ setProp(domElement, tag, propKey, propValue, props, null);
914
}
915
}
916
}
1065
}
1066
// defaultChecked and defaultValue are ignored by setProp
1067
default: {
1050
- setProp(domElement, tag, propKey, propValue, props);
1068
+ setProp(domElement, tag, propKey, propValue, props, null);
1069
}
1070
}
1071
}
1081
if (propValue == null) {
1082
continue;
1083
}
1066
- setPropOnCustomElement(domElement, tag, propKey, propValue, props);
1084
+ setPropOnCustomElement(
1085
+ domElement,
1086
+ tag,
1087
+ propKey,
1088
+ propValue,
1089
+ props,
1090
+ null,
1091
+ );
1092
}
1093
return;
1094
}
1103
if (propValue == null) {
1104
continue;
1105
}
1081
- setProp(domElement, tag, propKey, propValue, props);
1106
+ setProp(domElement, tag, propKey, propValue, props, null);
1107
}
1108
}
1109
1213
}
1214
if (styleUpdates) {
1215
if (__DEV__) {
1191
- validateShorthandPropertyCollisionInDev(styleUpdates, nextProps.style);
1216
+ validateShorthandPropertyCollisionInDev(lastProps.style, nextProps.style);
1217
}
1218
(updatePayload = updatePayload || []).push('style', styleUpdates);
1219
}
1220
return updatePayload;
1221
}
1222
1198
-// Apply the diff.
1223
export function updateProperties(
1224
+ domElement: Element,
1225
+ tag: string,
1226
+ lastProps: Object,
1227
+ nextProps: Object,
1228
+): void {
1229
+ if (__DEV__) {
1230
+ validatePropertiesInDevelopment(tag, nextProps);
1231
+ }
1232
+
1233
+ switch (tag) {
1234
+ case 'div':
1235
+ case 'span':
1236
+ case 'svg':
1237
+ case 'path':
1238
+ case 'a':
1239
+ case 'g':
1240
+ case 'p':
1241
+ case 'li': {
1242
+ // Fast track the most common tag types
1243
+ break;
1244
+ }
1245
+ case 'input': {
1246
+ // Update checked *before* name.
1247
+ // In the middle of an update, it is possible to have multiple checked.
1248
+ // When a checked radio tries to change name, browser makes another radio's checked false.
1249
+ if (nextProps.type === 'radio' && nextProps.name != null) {
1250
+ updateInputChecked(domElement, nextProps);
1251
+ }
1252
+ for (const propKey in lastProps) {
1253
+ const lastProp = lastProps[propKey];
1254
+ if (
1255
+ lastProps.hasOwnProperty(propKey) &&
1256
+ lastProp != null &&
1257
+ !nextProps.hasOwnProperty(propKey)
1258
+ ) {
1259
+ switch (propKey) {
1260
+ case 'checked': {
1261
+ const checked = nextProps.defaultChecked;
1262
+ const inputElement: HTMLInputElement = (domElement: any);
1263
+ inputElement.checked =
1264
+ !!checked &&
1265
+ typeof checked !== 'function' &&
1266
+ checked !== 'symbol';
1267
+ break;
1268
+ }
1269
+ case 'value': {
1270
+ // This is handled by updateWrapper below.
1271
+ break;
1272
+ }
1273
+ // defaultChecked and defaultValue are ignored by setProp
1274
+ default: {
1275
+ setProp(domElement, tag, propKey, null, nextProps, lastProp);
1276
+ }
1277
+ }
1278
+ }
1279
+ }
1280
+ for (const propKey in nextProps) {
1281
+ const nextProp = nextProps[propKey];
1282
+ const lastProp = lastProps[propKey];
1283
+ if (
1284
+ nextProps.hasOwnProperty(propKey) &&
1285
+ nextProp !== lastProp &&
1286
+ (nextProp != null || lastProp != null)
1287
+ ) {
1288
+ switch (propKey) {
1289
+ case 'checked': {
1290
+ const checked =
1291
+ nextProp != null ? nextProp : nextProps.defaultChecked;
1292
+ const inputElement: HTMLInputElement = (domElement: any);
1293
+ inputElement.checked =
1294
+ !!checked &&
1295
+ typeof checked !== 'function' &&
1296
+ checked !== 'symbol';
1297
+ break;
1298
+ }
1299
+ case 'value': {
1300
+ // This is handled by updateWrapper below.
1301
+ break;
1302
+ }
1303
+ case 'children':
1304
+ case 'dangerouslySetInnerHTML': {
1305
+ if (nextProp != null) {
1306
+ throw new Error(
1307
+ `${tag} is a void element tag and must neither have \`children\` nor ` +
1308
+ 'use `dangerouslySetInnerHTML`.',
1309
+ );
1310
+ }
1311
+ break;
1312
+ }
1313
+ // defaultChecked and defaultValue are ignored by setProp
1314
+ default: {
1315
+ setProp(domElement, tag, propKey, nextProp, nextProps, lastProp);
1316
+ }
1317
+ }
1318
+ }
1319
+ }
1320
+
1321
+ if (__DEV__) {
1322
+ const wasControlled =
1323
+ lastProps.type === 'checkbox' || lastProps.type === 'radio'
1324
+ ? lastProps.checked != null
1325
+ : lastProps.value != null;
1326
+ const isControlled =
1327
+ nextProps.type === 'checkbox' || nextProps.type === 'radio'
1328
+ ? nextProps.checked != null
1329
+ : nextProps.value != null;
1330
+
1331
+ if (
1332
+ !wasControlled &&
1333
+ isControlled &&
1334
+ !didWarnUncontrolledToControlled
1335
+ ) {
1336
+ console.error(
1337
+ 'A component is changing an uncontrolled input to be controlled. ' +
1338
+ 'This is likely caused by the value changing from undefined to ' +
1339
+ 'a defined value, which should not happen. ' +
1340
+ 'Decide between using a controlled or uncontrolled input ' +
1341
+ 'element for the lifetime of the component. More info: https://reactjs.org/link/controlled-components',
1342
+ );
1343
+ didWarnUncontrolledToControlled = true;
1344
+ }
1345
+ if (
1346
+ wasControlled &&
1347
+ !isControlled &&
1348
+ !didWarnControlledToUncontrolled
1349
+ ) {
1350
+ console.error(
1351
+ 'A component is changing a controlled input to be uncontrolled. ' +
1352
+ 'This is likely caused by the value changing from a defined to ' +
1353
+ 'undefined, which should not happen. ' +
1354
+ 'Decide between using a controlled or uncontrolled input ' +
1355
+ 'element for the lifetime of the component. More info: https://reactjs.org/link/controlled-components',
1356
+ );
1357
+ didWarnControlledToUncontrolled = true;
1358
+ }
1359
+ }
1360
+ // Update the wrapper around inputs *after* updating props. This has to
1361
+ // happen after updating the rest of props. Otherwise HTML5 input validations
1362
+ // raise warnings and prevent the new value from being assigned.
1363
+ updateInput(domElement, nextProps);
1364
+ return;
1365
+ }
1366
+ case 'select': {
1367
+ for (const propKey in lastProps) {
1368
+ const lastProp = lastProps[propKey];
1369
+ if (
1370
+ lastProps.hasOwnProperty(propKey) &&
1371
+ lastProp != null &&
1372
+ !nextProps.hasOwnProperty(propKey)
1373
+ ) {
1374
+ switch (propKey) {
1375
+ case 'value': {
1376
+ // This is handled by updateWrapper below.
1377
+ break;
1378
+ }
1379
+ // defaultValue are ignored by setProp
1380
+ default: {
1381
+ setProp(domElement, tag, propKey, null, nextProps, lastProp);
1382
+ }
1383
+ }
1384
+ }
1385
+ }
1386
+ for (const propKey in nextProps) {
1387
+ const nextProp = nextProps[propKey];
1388
+ const lastProp = lastProps[propKey];
1389
+ if (
1390
+ nextProps.hasOwnProperty(propKey) &&
1391
+ nextProp !== lastProp &&
1392
+ (nextProp != null || lastProp != null)
1393
+ ) {
1394
+ switch (propKey) {
1395
+ case 'value': {
1396
+ // This is handled by updateWrapper below.
1397
+ break;
1398
+ }
1399
+ // defaultValue are ignored by setProp
1400
+ default: {
1401
+ setProp(domElement, tag, propKey, nextProp, nextProps, lastProp);
1402
+ }
1403
+ }
1404
+ }
1405
+ }
1406
+ // <select> value update needs to occur after <option> children
1407
+ // reconciliation
1408
+ updateSelect(domElement, lastProps, nextProps);
1409
+ return;
1410
+ }
1411
+ case 'textarea': {
1412
+ for (const propKey in lastProps) {
1413
+ const lastProp = lastProps[propKey];
1414
+ if (
1415
+ lastProps.hasOwnProperty(propKey) &&
1416
+ lastProp != null &&
1417
+ !nextProps.hasOwnProperty(propKey)
1418
+ ) {
1419
+ switch (propKey) {
1420
+ case 'value': {
1421
+ // This is handled by updateWrapper below.
1422
+ break;
1423
+ }
1424
+ case 'children': {
1425
+ // TODO: This doesn't actually do anything if it updates.
1426
+ break;
1427
+ }
1428
+ // defaultValue is ignored by setProp
1429
+ default: {
1430
+ setProp(domElement, tag, propKey, null, nextProps, lastProp);
1431
+ }
1432
+ }
1433
+ }
1434
+ }
1435
+ for (const propKey in nextProps) {
1436
+ const nextProp = nextProps[propKey];
1437
+ const lastProp = lastProps[propKey];
1438
+ if (
1439
+ nextProps.hasOwnProperty(propKey) &&
1440
+ nextProp !== lastProp &&
1441
+ (nextProp != null || lastProp != null)
1442
+ ) {
1443
+ switch (propKey) {
1444
+ case 'value': {
1445
+ // This is handled by updateWrapper below.
1446
+ break;
1447
+ }
1448
+ case 'children': {
1449
+ // TODO: This doesn't actually do anything if it updates.
1450
+ break;
1451
+ }
1452
+ case 'dangerouslySetInnerHTML': {
1453
+ if (nextProp != null) {
1454
+ // TODO: Do we really need a special error message for this. It's also pretty blunt.
1455
+ throw new Error(
1456
+ '`dangerouslySetInnerHTML` does not make sense on <textarea>.',
1457
+ );
1458
+ }
1459
+ break;
1460
+ }
1461
+ // defaultValue is ignored by setProp
1462
+ default: {
1463
+ setProp(domElement, tag, propKey, nextProp, nextProps, lastProp);
1464
+ }
1465
+ }
1466
+ }
1467
+ }
1468
+ updateTextarea(domElement, nextProps);
1469
+ return;
1470
+ }
1471
+ case 'option': {
1472
+ for (const propKey in lastProps) {
1473
+ const lastProp = lastProps[propKey];
1474
+ if (
1475
+ lastProps.hasOwnProperty(propKey) &&
1476
+ lastProp != null &&
1477
+ !nextProps.hasOwnProperty(propKey)
1478
+ ) {
1479
+ switch (propKey) {
1480
+ case 'selected': {
1481
+ // TODO: Remove support for selected on option.
1482
+ (domElement: any).selected = false;
1483
+ break;
1484
+ }
1485
+ default: {
1486
+ setProp(domElement, tag, propKey, null, nextProps, lastProp);
1487
+ }
1488
+ }
1489
+ }
1490
+ }
1491
+ for (const propKey in nextProps) {
1492
+ const nextProp = nextProps[propKey];
1493
+ const lastProp = lastProps[propKey];
1494
+ if (
1495
+ nextProps.hasOwnProperty(propKey) &&
1496
+ nextProp !== lastProp &&
1497
+ (nextProp != null || lastProp != null)
1498
+ ) {
1499
+ switch (propKey) {
1500
+ case 'selected': {
1501
+ // TODO: Remove support for selected on option.
1502
+ (domElement: any).selected =
1503
+ nextProp &&
1504
+ typeof nextProp !== 'function' &&
1505
+ typeof nextProp !== 'symbol';
1506
+ break;
1507
+ }
1508
+ default: {
1509
+ setProp(domElement, tag, propKey, nextProp, nextProps, lastProp);
1510
+ }
1511
+ }
1512
+ }
1513
+ }
1514
+ return;
1515
+ }
1516
+ case 'img':
1517
+ case 'link':
1518
+ case 'area':
1519
+ case 'base':
1520
+ case 'br':
1521
+ case 'col':
1522
+ case 'embed':
1523
+ case 'hr':
1524
+ case 'keygen':
1525
+ case 'meta':
1526
+ case 'param':
1527
+ case 'source':
1528
+ case 'track':
1529
+ case 'wbr':
1530
+ case 'menuitem': {
1531
+ // Void elements
1532
+ for (const propKey in lastProps) {
1533
+ const lastProp = lastProps[propKey];
1534
+ if (
1535
+ lastProps.hasOwnProperty(propKey) &&
1536
+ lastProp != null &&
1537
+ !nextProps.hasOwnProperty(propKey)
1538
+ ) {
1539
+ setProp(domElement, tag, propKey, null, nextProps, lastProp);
1540
+ }
1541
+ }
1542
+ for (const propKey in nextProps) {
1543
+ const nextProp = nextProps[propKey];
1544
+ const lastProp = lastProps[propKey];
1545
+ if (
1546
+ nextProps.hasOwnProperty(propKey) &&
1547
+ nextProp !== lastProp &&
1548
+ (nextProp != null || lastProp != null)
1549
+ ) {
1550
+ switch (propKey) {
1551
+ case 'children':
1552
+ case 'dangerouslySetInnerHTML': {
1553
+ if (nextProp != null) {
1554
+ // TODO: Can we make this a DEV warning to avoid this deny list?
1555
+ throw new Error(
1556
+ `${tag} is a void element tag and must neither have \`children\` nor ` +
1557
+ 'use `dangerouslySetInnerHTML`.',
1558
+ );
1559
+ }
1560
+ break;
1561
+ }
1562
+ // defaultChecked and defaultValue are ignored by setProp
1563
+ default: {
1564
+ setProp(domElement, tag, propKey, nextProp, nextProps, lastProp);
1565
+ }
1566
+ }
1567
+ }
1568
+ }
1569
+ return;
1570
+ }
1571
+ default: {
1572
+ if (isCustomElement(tag, nextProps)) {
1573
+ for (const propKey in lastProps) {
1574
+ const lastProp = lastProps[propKey];
1575
+ if (
1576
+ lastProps.hasOwnProperty(propKey) &&
1577
+ lastProp != null &&
1578
+ !nextProps.hasOwnProperty(propKey)
1579
+ ) {
1580
+ setPropOnCustomElement(
1581
+ domElement,
1582
+ tag,
1583
+ propKey,
1584
+ null,
1585
+ nextProps,
1586
+ lastProp,
1587
+ );
1588
+ }
1589
+ }
1590
+ for (const propKey in nextProps) {
1591
+ const nextProp = nextProps[propKey];
1592
+ const lastProp = lastProps[propKey];
1593
+ if (
1594
+ nextProps.hasOwnProperty(propKey) &&
1595
+ nextProp !== lastProp &&
1596
+ (nextProp != null || lastProp != null)
1597
+ ) {
1598
+ setPropOnCustomElement(
1599
+ domElement,
1600
+ tag,
1601
+ propKey,
1602
+ nextProp,
1603
+ nextProps,
1604
+ lastProp,
1605
+ );
1606
+ }
1607
+ }
1608
+ return;
1609
+ }
1610
+ }
1611
+ }
1612
+
1613
+ for (const propKey in lastProps) {
1614
+ const lastProp = lastProps[propKey];
1615
+ if (
1616
+ lastProps.hasOwnProperty(propKey) &&
1617
+ lastProp != null &&
1618
+ !nextProps.hasOwnProperty(propKey)
1619
+ ) {
1620
+ setProp(domElement, tag, propKey, null, nextProps, lastProp);
1621
+ }
1622
+ }
1623
+ for (const propKey in nextProps) {
1624
+ const nextProp = nextProps[propKey];
1625
+ const lastProp = lastProps[propKey];
1626
+ if (
1627
+ nextProps.hasOwnProperty(propKey) &&
1628
+ nextProp !== lastProp &&
1629
+ (nextProp != null || lastProp != null)
1630
+ ) {
1631
+ setProp(domElement, tag, propKey, nextProp, nextProps, lastProp);
1632
+ }
1633
+ }
1634
+}
1635
+
1636
+// Apply the diff.
1637
+export function updatePropertiesWithDiff(
1638
domElement: Element,
1639
updatePayload: Array<any>,
1640
tag: string,
1690
}
1691
// defaultChecked and defaultValue are ignored by setProp
1692
default: {
1255
- setProp(domElement, tag, propKey, propValue, nextProps);
1693
+ setProp(domElement, tag, propKey, propValue, nextProps, null);
1694
}
1695
}
1696
}
1751
}
1752
// defaultValue are ignored by setProp
1753
default: {
1316
- setProp(domElement, tag, propKey, propValue, nextProps);
1754
+ setProp(domElement, tag, propKey, propValue, nextProps, null);
1755
}
1756
}
1757
}
1784
}
1785
// defaultValue is ignored by setProp
1786
default: {
1349
- setProp(domElement, tag, propKey, propValue, nextProps);
1787
+ setProp(domElement, tag, propKey, propValue, nextProps, null);
1788
}
1789
}
1790
}
1805
break;
1806
}
1807
default: {
1370
- setProp(domElement, tag, propKey, propValue, nextProps);
1808
+ setProp(domElement, tag, propKey, propValue, nextProps, null);
1809
}
1810
}
1811
}
1844
}
1845
// defaultChecked and defaultValue are ignored by setProp
1846
default: {
1409
- setProp(domElement, tag, propKey, propValue, nextProps);
1847
+ setProp(domElement, tag, propKey, propValue, nextProps, null);
1848
}
1849
}
1850
}
1861
propKey,
1862
propValue,
1863
nextProps,
1864
+ null,
1865
);
1866
}
1867
return;
1873
for (let i = 0; i < updatePayload.length; i += 2) {
1874
const propKey = updatePayload[i];
1875
const propValue = updatePayload[i + 1];
1437
- setProp(domElement, tag, propKey, propValue, nextProps);
1876
+ setProp(domElement, tag, propKey, propValue, nextProps, null);
1877
}
1878
}
1879
2816
);
2817
}
2818
if (!isConcurrentMode || !enableClientRenderFallbackOnTextMismatch) {
2380
- updatePayload = ['children', children];
2819
+ if (diffInCommitPhase) {
2820
+ // We really should be patching this in the commit phase but since
2821
+ // this only affects legacy mode hydration which is deprecated anyway
2822
+ // we can get away with it.
2823
+ // Host singletons get their children appended and don't use the text
2824
+ // content mechanism.
2825
+ if (!enableHostSingletons || tag !== 'body') {
2826
+ domElement.textContent = (children: any);
2827
+ }
2828
+ } else {
2829
+ updatePayload = ['children', children];
2830
+ }
2831
}
2832
}
2833
}