Skip to content

Commit afecd4e

Browse files
committed
Add expertimental optimisticKey behind a flag (facebook#35162)
When dealing with optimistic state, a common problem is not knowing the id of the thing we're waiting on. Items in lists need keys (and single items should often have keys too to reset their state). As a result you have to generate fake keys. It's a pain to manage those and when the real item comes in, you often end up rendering that with a different `key` which resets the state of the component tree. That in turns works against the grain of React and a lot of negatives fall out of it. This adds a special `optimisticKey` symbol that can be used in place of a `string` key. ```js import {optimisticKey} from 'react'; ... const [optimisticItems, setOptimisticItems] = useOptimistic([]); const children = savedItems.concat( optimisticItems.map(item => <Item key={optimisticKey} item={item} /> ) ); return <div>{children}</div>; ``` The semantics of this `optimisticKey` is that the assumption is that the newly saved item will be rendered in the same slot as the previous optimistic items. State is transferred into whatever real key ends up in the same slot. This might lead to some incorrect transferring of state in some cases where things don't end up lining up - but it's worth it for simplicity in many cases since dealing with true matching of optimistic state is often very complex for something that only lasts a blink of an eye. If a new item matches a `key` elsewhere in the set, then that's favored over reconciling against the old slot. One quirk with the current algorithm is if the `savedItems` has items removed, then the slots won't line up by index anymore and will be skewed. We might be able to add something where the optimistic set is always reconciled against the end. However, it's probably better to just assume that the set will line up perfectly and otherwise it's just best effort that can lead to weird artifacts. An `optimisticKey` will match itself for updates to the same slot, but it will not match any existing slot that is not an `optimisticKey`. So it's not an `any`, which I originally called it, because it doesn't match existing real keys against new optimistic keys. Only one direction. DiffTrain build for [eb89912](facebook@eb89912)
1 parent 64940f0 commit afecd4e

File tree

21 files changed

+309
-239
lines changed

21 files changed

+309
-239
lines changed

compiled-rn/VERSION_NATIVE_FB

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
19.3.0-native-fb-0972e239-20251118
1+
19.3.0-native-fb-eb89912e-20251118

compiled-rn/facebook-fbsource/xplat/js/RKJSModules/vendor/react/react-dom/cjs/ReactDOM-dev.js

Lines changed: 24 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
* @noflow
88
* @nolint
99
* @preventMunge
10-
* @generated SignedSource<<ef0d59e78249c4df3f20526eeb2f7ef1>>
10+
* @generated SignedSource<<dbe5dba41a0083047349811a0f5d66ce>>
1111
*/
1212

1313
"use strict";
@@ -20,25 +20,30 @@ __DEV__ &&
2020
function createPortal$1(children, containerInfo, implementation) {
2121
var key =
2222
3 < arguments.length && void 0 !== arguments[3] ? arguments[3] : null;
23-
try {
24-
testStringCoercion(key);
25-
var JSCompiler_inline_result = !1;
26-
} catch (e) {
27-
JSCompiler_inline_result = !0;
23+
if (null == key) key = null;
24+
else if (key === REACT_OPTIMISTIC_KEY) key = REACT_OPTIMISTIC_KEY;
25+
else {
26+
try {
27+
testStringCoercion(key);
28+
var JSCompiler_inline_result = !1;
29+
} catch (e) {
30+
JSCompiler_inline_result = !0;
31+
}
32+
JSCompiler_inline_result &&
33+
(console.error(
34+
"The provided key is an unsupported type %s. This value must be coerced to a string before using it here.",
35+
("function" === typeof Symbol &&
36+
Symbol.toStringTag &&
37+
key[Symbol.toStringTag]) ||
38+
key.constructor.name ||
39+
"Object"
40+
),
41+
testStringCoercion(key));
42+
key = "" + key;
2843
}
29-
JSCompiler_inline_result &&
30-
(console.error(
31-
"The provided key is an unsupported type %s. This value must be coerced to a string before using it here.",
32-
("function" === typeof Symbol &&
33-
Symbol.toStringTag &&
34-
key[Symbol.toStringTag]) ||
35-
key.constructor.name ||
36-
"Object"
37-
),
38-
testStringCoercion(key));
3944
return {
4045
$$typeof: REACT_PORTAL_TYPE,
41-
key: null == key ? null : "" + key,
46+
key: key,
4247
children: children,
4348
containerInfo: containerInfo,
4449
implementation: implementation
@@ -100,6 +105,7 @@ __DEV__ &&
100105
findDOMNode: null
101106
},
102107
REACT_PORTAL_TYPE = Symbol.for("react.portal"),
108+
REACT_OPTIMISTIC_KEY = Symbol.for("react.optimistic_key"),
103109
ReactSharedInternals =
104110
React.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE;
105111
("function" === typeof Map &&
@@ -404,5 +410,5 @@ __DEV__ &&
404410
exports.useFormStatus = function () {
405411
return resolveDispatcher().useHostTransitionStatus();
406412
};
407-
exports.version = "19.3.0-native-fb-0972e239-20251118";
413+
exports.version = "19.3.0-native-fb-eb89912e-20251118";
408414
})();

compiled-rn/facebook-fbsource/xplat/js/RKJSModules/vendor/react/react-dom/cjs/ReactDOM-prod.js

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
* @noflow
88
* @nolint
99
* @preventMunge
10-
* @generated SignedSource<<9314c03d513105f3f1d8147a0db7b4c5>>
10+
* @generated SignedSource<<1d08fd796f20004fef1c064d7ca54e75>>
1111
*/
1212

1313
"use strict";
@@ -45,13 +45,19 @@ var Internals = {
4545
p: 0,
4646
findDOMNode: null
4747
},
48-
REACT_PORTAL_TYPE = Symbol.for("react.portal");
48+
REACT_PORTAL_TYPE = Symbol.for("react.portal"),
49+
REACT_OPTIMISTIC_KEY = Symbol.for("react.optimistic_key");
4950
function createPortal$1(children, containerInfo, implementation) {
5051
var key =
5152
3 < arguments.length && void 0 !== arguments[3] ? arguments[3] : null;
5253
return {
5354
$$typeof: REACT_PORTAL_TYPE,
54-
key: null == key ? null : "" + key,
55+
key:
56+
null == key
57+
? null
58+
: key === REACT_OPTIMISTIC_KEY
59+
? REACT_OPTIMISTIC_KEY
60+
: "" + key,
5561
children: children,
5662
containerInfo: containerInfo,
5763
implementation: implementation
@@ -203,4 +209,4 @@ exports.useFormState = function (action, initialState, permalink) {
203209
exports.useFormStatus = function () {
204210
return ReactSharedInternals.H.useHostTransitionStatus();
205211
};
206-
exports.version = "19.3.0-native-fb-0972e239-20251118";
212+
exports.version = "19.3.0-native-fb-eb89912e-20251118";

compiled-rn/facebook-fbsource/xplat/js/RKJSModules/vendor/react/react-dom/cjs/ReactDOM-profiling.js

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
* @noflow
88
* @nolint
99
* @preventMunge
10-
* @generated SignedSource<<9314c03d513105f3f1d8147a0db7b4c5>>
10+
* @generated SignedSource<<1d08fd796f20004fef1c064d7ca54e75>>
1111
*/
1212

1313
"use strict";
@@ -45,13 +45,19 @@ var Internals = {
4545
p: 0,
4646
findDOMNode: null
4747
},
48-
REACT_PORTAL_TYPE = Symbol.for("react.portal");
48+
REACT_PORTAL_TYPE = Symbol.for("react.portal"),
49+
REACT_OPTIMISTIC_KEY = Symbol.for("react.optimistic_key");
4950
function createPortal$1(children, containerInfo, implementation) {
5051
var key =
5152
3 < arguments.length && void 0 !== arguments[3] ? arguments[3] : null;
5253
return {
5354
$$typeof: REACT_PORTAL_TYPE,
54-
key: null == key ? null : "" + key,
55+
key:
56+
null == key
57+
? null
58+
: key === REACT_OPTIMISTIC_KEY
59+
? REACT_OPTIMISTIC_KEY
60+
: "" + key,
5561
children: children,
5662
containerInfo: containerInfo,
5763
implementation: implementation
@@ -203,4 +209,4 @@ exports.useFormState = function (action, initialState, permalink) {
203209
exports.useFormStatus = function () {
204210
return ReactSharedInternals.H.useHostTransitionStatus();
205211
};
206-
exports.version = "19.3.0-native-fb-0972e239-20251118";
212+
exports.version = "19.3.0-native-fb-eb89912e-20251118";

compiled-rn/facebook-fbsource/xplat/js/RKJSModules/vendor/react/react-dom/cjs/ReactDOMClient-dev.js

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
* @noflow
88
* @nolint
99
* @preventMunge
10-
* @generated SignedSource<<c2434ae0950f64f4b1543bc301a17592>>
10+
* @generated SignedSource<<582860e227f829c8d87e583d8522e0dc>>
1111
*/
1212

1313
/*
@@ -6527,9 +6527,9 @@ __DEV__ &&
65276527
}
65286528
function mapRemainingChildren(currentFirstChild) {
65296529
for (var existingChildren = new Map(); null !== currentFirstChild; )
6530-
null !== currentFirstChild.key
6531-
? existingChildren.set(currentFirstChild.key, currentFirstChild)
6532-
: existingChildren.set(currentFirstChild.index, currentFirstChild),
6530+
null === currentFirstChild.key
6531+
? existingChildren.set(currentFirstChild.index, currentFirstChild)
6532+
: existingChildren.set(currentFirstChild.key, currentFirstChild),
65336533
(currentFirstChild = currentFirstChild.sibling);
65346534
return existingChildren;
65356535
}
@@ -7049,10 +7049,11 @@ __DEV__ &&
70497049
knownKeys
70507050
)),
70517051
shouldTrackSideEffects &&
7052-
null !== nextOldFiber.alternate &&
7053-
oldFiber.delete(
7054-
null === nextOldFiber.key ? newIdx : nextOldFiber.key
7055-
),
7052+
((newFiber = nextOldFiber.alternate),
7053+
null !== newFiber &&
7054+
oldFiber.delete(
7055+
null === newFiber.key ? newIdx : newFiber.key
7056+
)),
70567057
(currentFirstChild = placeChild(
70577058
nextOldFiber,
70587059
currentFirstChild,
@@ -7161,10 +7162,9 @@ __DEV__ &&
71617162
knownKeys
71627163
)),
71637164
shouldTrackSideEffects &&
7164-
null !== nextOldFiber.alternate &&
7165-
oldFiber.delete(
7166-
null === nextOldFiber.key ? newIdx : nextOldFiber.key
7167-
),
7165+
((step = nextOldFiber.alternate),
7166+
null !== step &&
7167+
oldFiber.delete(null === step.key ? newIdx : step.key)),
71687168
(currentFirstChild = placeChild(
71697169
nextOldFiber,
71707170
currentFirstChild,
@@ -30242,11 +30242,11 @@ __DEV__ &&
3024230242
};
3024330243
(function () {
3024430244
var isomorphicReactPackageVersion = React.version;
30245-
if ("19.3.0-native-fb-0972e239-20251118" !== isomorphicReactPackageVersion)
30245+
if ("19.3.0-native-fb-eb89912e-20251118" !== isomorphicReactPackageVersion)
3024630246
throw Error(
3024730247
'Incompatible React versions: The "react" and "react-dom" packages must have the exact same version. Instead got:\n - react: ' +
3024830248
(isomorphicReactPackageVersion +
30249-
"\n - react-dom: 19.3.0-native-fb-0972e239-20251118\nLearn more: https://react.dev/warnings/version-mismatch")
30249+
"\n - react-dom: 19.3.0-native-fb-eb89912e-20251118\nLearn more: https://react.dev/warnings/version-mismatch")
3025030250
);
3025130251
})();
3025230252
("function" === typeof Map &&
@@ -30283,10 +30283,10 @@ __DEV__ &&
3028330283
!(function () {
3028430284
var internals = {
3028530285
bundleType: 1,
30286-
version: "19.3.0-native-fb-0972e239-20251118",
30286+
version: "19.3.0-native-fb-eb89912e-20251118",
3028730287
rendererPackageName: "react-dom",
3028830288
currentDispatcherRef: ReactSharedInternals,
30289-
reconcilerVersion: "19.3.0-native-fb-0972e239-20251118"
30289+
reconcilerVersion: "19.3.0-native-fb-eb89912e-20251118"
3029030290
};
3029130291
internals.overrideHookState = overrideHookState;
3029230292
internals.overrideHookStateDeletePath = overrideHookStateDeletePath;
@@ -30436,5 +30436,5 @@ __DEV__ &&
3043630436
listenToAllSupportedEvents(container);
3043730437
return new ReactDOMHydrationRoot(initialChildren);
3043830438
};
30439-
exports.version = "19.3.0-native-fb-0972e239-20251118";
30439+
exports.version = "19.3.0-native-fb-eb89912e-20251118";
3044030440
})();

compiled-rn/facebook-fbsource/xplat/js/RKJSModules/vendor/react/react-dom/cjs/ReactDOMClient-prod.js

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
* @noflow
88
* @nolint
99
* @preventMunge
10-
* @generated SignedSource<<898425b291157a203fbed210aa06d2d1>>
10+
* @generated SignedSource<<d90bb2baf6a7cce491b6b20152b9576c>>
1111
*/
1212

1313
/*
@@ -3578,9 +3578,9 @@ function createChildReconciler(shouldTrackSideEffects) {
35783578
}
35793579
function mapRemainingChildren(currentFirstChild) {
35803580
for (var existingChildren = new Map(); null !== currentFirstChild; )
3581-
null !== currentFirstChild.key
3582-
? existingChildren.set(currentFirstChild.key, currentFirstChild)
3583-
: existingChildren.set(currentFirstChild.index, currentFirstChild),
3581+
null === currentFirstChild.key
3582+
? existingChildren.set(currentFirstChild.index, currentFirstChild)
3583+
: existingChildren.set(currentFirstChild.key, currentFirstChild),
35843584
(currentFirstChild = currentFirstChild.sibling);
35853585
return existingChildren;
35863586
}
@@ -3958,10 +3958,9 @@ function createChildReconciler(shouldTrackSideEffects) {
39583958
)),
39593959
null !== nextOldFiber &&
39603960
(shouldTrackSideEffects &&
3961-
null !== nextOldFiber.alternate &&
3962-
oldFiber.delete(
3963-
null === nextOldFiber.key ? newIdx : nextOldFiber.key
3964-
),
3961+
((newFiber = nextOldFiber.alternate),
3962+
null !== newFiber &&
3963+
oldFiber.delete(null === newFiber.key ? newIdx : newFiber.key)),
39653964
(currentFirstChild = placeChild(
39663965
nextOldFiber,
39673966
currentFirstChild,
@@ -4040,8 +4039,11 @@ function createChildReconciler(shouldTrackSideEffects) {
40404039
(step = updateFromMap(oldFiber, returnFiber, newIdx, step.value, lanes)),
40414040
null !== step &&
40424041
(shouldTrackSideEffects &&
4043-
null !== step.alternate &&
4044-
oldFiber.delete(null === step.key ? newIdx : step.key),
4042+
((nextOldFiber = step.alternate),
4043+
null !== nextOldFiber &&
4044+
oldFiber.delete(
4045+
null === nextOldFiber.key ? newIdx : nextOldFiber.key
4046+
)),
40454047
(currentFirstChild = placeChild(step, currentFirstChild, newIdx)),
40464048
null === previousNewFiber
40474049
? (resultingFirstChild = step)
@@ -17702,14 +17704,14 @@ ReactDOMHydrationRoot.prototype.unstable_scheduleHydration = function (target) {
1770217704
};
1770317705
var isomorphicReactPackageVersion$jscomp$inline_2054 = React.version;
1770417706
if (
17705-
"19.3.0-native-fb-0972e239-20251118" !==
17707+
"19.3.0-native-fb-eb89912e-20251118" !==
1770617708
isomorphicReactPackageVersion$jscomp$inline_2054
1770717709
)
1770817710
throw Error(
1770917711
formatProdErrorMessage(
1771017712
527,
1771117713
isomorphicReactPackageVersion$jscomp$inline_2054,
17712-
"19.3.0-native-fb-0972e239-20251118"
17714+
"19.3.0-native-fb-eb89912e-20251118"
1771317715
)
1771417716
);
1771517717
ReactDOMSharedInternals.findDOMNode = function (componentOrElement) {
@@ -17731,10 +17733,10 @@ ReactDOMSharedInternals.findDOMNode = function (componentOrElement) {
1773117733
};
1773217734
var internals$jscomp$inline_2636 = {
1773317735
bundleType: 0,
17734-
version: "19.3.0-native-fb-0972e239-20251118",
17736+
version: "19.3.0-native-fb-eb89912e-20251118",
1773517737
rendererPackageName: "react-dom",
1773617738
currentDispatcherRef: ReactSharedInternals,
17737-
reconcilerVersion: "19.3.0-native-fb-0972e239-20251118"
17739+
reconcilerVersion: "19.3.0-native-fb-eb89912e-20251118"
1773817740
};
1773917741
if ("undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__) {
1774017742
var hook$jscomp$inline_2637 = __REACT_DEVTOOLS_GLOBAL_HOOK__;
@@ -17841,4 +17843,4 @@ exports.hydrateRoot = function (container, initialChildren, options) {
1784117843
listenToAllSupportedEvents(container);
1784217844
return new ReactDOMHydrationRoot(initialChildren);
1784317845
};
17844-
exports.version = "19.3.0-native-fb-0972e239-20251118";
17846+
exports.version = "19.3.0-native-fb-eb89912e-20251118";

0 commit comments

Comments
 (0)