Skip to content

Commit 9c65b7e

Browse files
committed
Implement Navigation API backed default indicator for DOM renderer (#33162)
Stacked on #33160. By default, if `onDefaultTransitionIndicator` is not overridden, this will trigger a fake Navigation event using the Navigation API. This is intercepted to create an on-going navigation until we complete the Transition. Basically each default Transition is simulated as a Navigation. This triggers the native browser loading state (in Chrome at least). So now by default the browser spinner spins during a Transition if no other loading state is provided. Firefox and Safari hasn't shipped Navigation API yet and even in the flag Safari has, it doesn't actually trigger the native loading state. To ensures that you can still use other Navigations concurrently, we don't start our fake Navigation if there's one on-going already. Similarly if our fake Navigation gets interrupted by another. We wait for on-going ones to finish and then start a new fake one if we're supposed to be still pending. There might be other routers on the page that might listen to intercept Navigation Events. Typically you'd expect them not to trigger a refetch when navigating to the same state. However, if they want to detect this we provide the `"react-transition"` string in the `info` field for this purpose. DiffTrain build for [5944042](5944042)
1 parent ffe6305 commit 9c65b7e

24 files changed

+365
-93
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.2.0-native-fb-b480865d-20250513
1+
19.2.0-native-fb-59440424-20250513

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
* @noflow
88
* @nolint
99
* @preventMunge
10-
* @generated SignedSource<<0ab47b372b965d3d02258f47dc0402f2>>
10+
* @generated SignedSource<<dec5042bc5f03cc4b5905cbafac45d5d>>
1111
*/
1212

1313
"use strict";
@@ -404,5 +404,5 @@ __DEV__ &&
404404
exports.useFormStatus = function () {
405405
return resolveDispatcher().useHostTransitionStatus();
406406
};
407-
exports.version = "19.2.0-native-fb-b480865d-20250513";
407+
exports.version = "19.2.0-native-fb-59440424-20250513";
408408
})();

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
* @noflow
88
* @nolint
99
* @preventMunge
10-
* @generated SignedSource<<8818e9dff1a8e3a8643381d87dda4bd0>>
10+
* @generated SignedSource<<2e17e4696030242c7e8aced13f53e62a>>
1111
*/
1212

1313
"use strict";
@@ -203,4 +203,4 @@ exports.useFormState = function (action, initialState, permalink) {
203203
exports.useFormStatus = function () {
204204
return ReactSharedInternals.H.useHostTransitionStatus();
205205
};
206-
exports.version = "19.2.0-native-fb-b480865d-20250513";
206+
exports.version = "19.2.0-native-fb-59440424-20250513";

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
* @noflow
88
* @nolint
99
* @preventMunge
10-
* @generated SignedSource<<8818e9dff1a8e3a8643381d87dda4bd0>>
10+
* @generated SignedSource<<2e17e4696030242c7e8aced13f53e62a>>
1111
*/
1212

1313
"use strict";
@@ -203,4 +203,4 @@ exports.useFormState = function (action, initialState, permalink) {
203203
exports.useFormStatus = function () {
204204
return ReactSharedInternals.H.useHostTransitionStatus();
205205
};
206-
exports.version = "19.2.0-native-fb-b480865d-20250513";
206+
exports.version = "19.2.0-native-fb-59440424-20250513";

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

Lines changed: 57 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
* @noflow
88
* @nolint
99
* @preventMunge
10-
* @generated SignedSource<<6df1c2dec5d32d3e650052606abec7b1>>
10+
* @generated SignedSource<<030115b4e4980785e31f5c7ff2c5cc14>>
1111
*/
1212

1313
/*
@@ -23088,7 +23088,57 @@ __DEV__ &&
2308823088
}
2308923089
}
2309023090
function defaultOnDefaultTransitionIndicator() {
23091-
return function () {};
23091+
function handleNavigate(event) {
23092+
event.canIntercept &&
23093+
"react-transition" === event.info &&
23094+
event.intercept({
23095+
handler: function () {
23096+
return new Promise(function (resolve) {
23097+
return (pendingResolve = resolve);
23098+
});
23099+
},
23100+
focusReset: "manual",
23101+
scroll: "manual"
23102+
});
23103+
}
23104+
function handleNavigateComplete() {
23105+
null !== pendingResolve && (pendingResolve(), (pendingResolve = null));
23106+
isCancelled || startFakeNavigation();
23107+
}
23108+
function startFakeNavigation() {
23109+
if (!isCancelled && !navigation.transition) {
23110+
var currentEntry = navigation.currentEntry;
23111+
currentEntry &&
23112+
null != currentEntry.url &&
23113+
navigation.navigate(currentEntry.url, {
23114+
state: currentEntry.getState(),
23115+
info: "react-transition",
23116+
history: "replace"
23117+
});
23118+
}
23119+
}
23120+
if ("object" === typeof navigation) {
23121+
var isCancelled = !1,
23122+
pendingResolve = null;
23123+
navigation.addEventListener("navigate", handleNavigate);
23124+
navigation.addEventListener("navigatesuccess", handleNavigateComplete);
23125+
navigation.addEventListener("navigateerror", handleNavigateComplete);
23126+
setTimeout(startFakeNavigation, 100);
23127+
return function () {
23128+
isCancelled = !0;
23129+
navigation.removeEventListener("navigate", handleNavigate);
23130+
navigation.removeEventListener(
23131+
"navigatesuccess",
23132+
handleNavigateComplete
23133+
);
23134+
navigation.removeEventListener(
23135+
"navigateerror",
23136+
handleNavigateComplete
23137+
);
23138+
null !== pendingResolve &&
23139+
(pendingResolve(), (pendingResolve = null));
23140+
};
23141+
}
2309223142
}
2309323143
function ReactDOMRoot(internalRoot) {
2309423144
this._internalRoot = internalRoot;
@@ -26960,11 +27010,11 @@ __DEV__ &&
2696027010
};
2696127011
(function () {
2696227012
var isomorphicReactPackageVersion = React.version;
26963-
if ("19.2.0-native-fb-b480865d-20250513" !== isomorphicReactPackageVersion)
27013+
if ("19.2.0-native-fb-59440424-20250513" !== isomorphicReactPackageVersion)
2696427014
throw Error(
2696527015
'Incompatible React versions: The "react" and "react-dom" packages must have the exact same version. Instead got:\n - react: ' +
2696627016
(isomorphicReactPackageVersion +
26967-
"\n - react-dom: 19.2.0-native-fb-b480865d-20250513\nLearn more: https://react.dev/warnings/version-mismatch")
27017+
"\n - react-dom: 19.2.0-native-fb-59440424-20250513\nLearn more: https://react.dev/warnings/version-mismatch")
2696827018
);
2696927019
})();
2697027020
("function" === typeof Map &&
@@ -27001,10 +27051,10 @@ __DEV__ &&
2700127051
!(function () {
2700227052
var internals = {
2700327053
bundleType: 1,
27004-
version: "19.2.0-native-fb-b480865d-20250513",
27054+
version: "19.2.0-native-fb-59440424-20250513",
2700527055
rendererPackageName: "react-dom",
2700627056
currentDispatcherRef: ReactSharedInternals,
27007-
reconcilerVersion: "19.2.0-native-fb-b480865d-20250513"
27057+
reconcilerVersion: "19.2.0-native-fb-59440424-20250513"
2700827058
};
2700927059
internals.overrideHookState = overrideHookState;
2701027060
internals.overrideHookStateDeletePath = overrideHookStateDeletePath;
@@ -27142,5 +27192,5 @@ __DEV__ &&
2714227192
listenToAllSupportedEvents(container);
2714327193
return new ReactDOMHydrationRoot(initialChildren);
2714427194
};
27145-
exports.version = "19.2.0-native-fb-b480865d-20250513";
27195+
exports.version = "19.2.0-native-fb-59440424-20250513";
2714627196
})();

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

Lines changed: 50 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
* @noflow
88
* @nolint
99
* @preventMunge
10-
* @generated SignedSource<<a7fa48d321866d402c60ee70cf639689>>
10+
* @generated SignedSource<<5bd08c285c559e1d555ed66bced9133a>>
1111
*/
1212

1313
/*
@@ -17006,7 +17006,50 @@ function retryIfBlockedOn(unblocked) {
1700617006
}
1700717007
}
1700817008
function defaultOnDefaultTransitionIndicator() {
17009-
return function () {};
17009+
function handleNavigate(event) {
17010+
event.canIntercept &&
17011+
"react-transition" === event.info &&
17012+
event.intercept({
17013+
handler: function () {
17014+
return new Promise(function (resolve) {
17015+
return (pendingResolve = resolve);
17016+
});
17017+
},
17018+
focusReset: "manual",
17019+
scroll: "manual"
17020+
});
17021+
}
17022+
function handleNavigateComplete() {
17023+
null !== pendingResolve && (pendingResolve(), (pendingResolve = null));
17024+
isCancelled || startFakeNavigation();
17025+
}
17026+
function startFakeNavigation() {
17027+
if (!isCancelled && !navigation.transition) {
17028+
var currentEntry = navigation.currentEntry;
17029+
currentEntry &&
17030+
null != currentEntry.url &&
17031+
navigation.navigate(currentEntry.url, {
17032+
state: currentEntry.getState(),
17033+
info: "react-transition",
17034+
history: "replace"
17035+
});
17036+
}
17037+
}
17038+
if ("object" === typeof navigation) {
17039+
var isCancelled = !1,
17040+
pendingResolve = null;
17041+
navigation.addEventListener("navigate", handleNavigate);
17042+
navigation.addEventListener("navigatesuccess", handleNavigateComplete);
17043+
navigation.addEventListener("navigateerror", handleNavigateComplete);
17044+
setTimeout(startFakeNavigation, 100);
17045+
return function () {
17046+
isCancelled = !0;
17047+
navigation.removeEventListener("navigate", handleNavigate);
17048+
navigation.removeEventListener("navigatesuccess", handleNavigateComplete);
17049+
navigation.removeEventListener("navigateerror", handleNavigateComplete);
17050+
null !== pendingResolve && (pendingResolve(), (pendingResolve = null));
17051+
};
17052+
}
1701017053
}
1701117054
function ReactDOMRoot(internalRoot) {
1701217055
this._internalRoot = internalRoot;
@@ -17051,14 +17094,14 @@ ReactDOMHydrationRoot.prototype.unstable_scheduleHydration = function (target) {
1705117094
};
1705217095
var isomorphicReactPackageVersion$jscomp$inline_2015 = React.version;
1705317096
if (
17054-
"19.2.0-native-fb-b480865d-20250513" !==
17097+
"19.2.0-native-fb-59440424-20250513" !==
1705517098
isomorphicReactPackageVersion$jscomp$inline_2015
1705617099
)
1705717100
throw Error(
1705817101
formatProdErrorMessage(
1705917102
527,
1706017103
isomorphicReactPackageVersion$jscomp$inline_2015,
17061-
"19.2.0-native-fb-b480865d-20250513"
17104+
"19.2.0-native-fb-59440424-20250513"
1706217105
)
1706317106
);
1706417107
ReactDOMSharedInternals.findDOMNode = function (componentOrElement) {
@@ -17080,10 +17123,10 @@ ReactDOMSharedInternals.findDOMNode = function (componentOrElement) {
1708017123
};
1708117124
var internals$jscomp$inline_2534 = {
1708217125
bundleType: 0,
17083-
version: "19.2.0-native-fb-b480865d-20250513",
17126+
version: "19.2.0-native-fb-59440424-20250513",
1708417127
rendererPackageName: "react-dom",
1708517128
currentDispatcherRef: ReactSharedInternals,
17086-
reconcilerVersion: "19.2.0-native-fb-b480865d-20250513"
17129+
reconcilerVersion: "19.2.0-native-fb-59440424-20250513"
1708717130
};
1708817131
if ("undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__) {
1708917132
var hook$jscomp$inline_2535 = __REACT_DEVTOOLS_GLOBAL_HOOK__;
@@ -17181,4 +17224,4 @@ exports.hydrateRoot = function (container, initialChildren, options) {
1718117224
listenToAllSupportedEvents(container);
1718217225
return new ReactDOMHydrationRoot(initialChildren);
1718317226
};
17184-
exports.version = "19.2.0-native-fb-b480865d-20250513";
17227+
exports.version = "19.2.0-native-fb-59440424-20250513";

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

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

1313
/*
@@ -17716,7 +17716,50 @@ function retryIfBlockedOn(unblocked) {
1771617716
}
1771717717
}
1771817718
function defaultOnDefaultTransitionIndicator() {
17719-
return function () {};
17719+
function handleNavigate(event) {
17720+
event.canIntercept &&
17721+
"react-transition" === event.info &&
17722+
event.intercept({
17723+
handler: function () {
17724+
return new Promise(function (resolve) {
17725+
return (pendingResolve = resolve);
17726+
});
17727+
},
17728+
focusReset: "manual",
17729+
scroll: "manual"
17730+
});
17731+
}
17732+
function handleNavigateComplete() {
17733+
null !== pendingResolve && (pendingResolve(), (pendingResolve = null));
17734+
isCancelled || startFakeNavigation();
17735+
}
17736+
function startFakeNavigation() {
17737+
if (!isCancelled && !navigation.transition) {
17738+
var currentEntry = navigation.currentEntry;
17739+
currentEntry &&
17740+
null != currentEntry.url &&
17741+
navigation.navigate(currentEntry.url, {
17742+
state: currentEntry.getState(),
17743+
info: "react-transition",
17744+
history: "replace"
17745+
});
17746+
}
17747+
}
17748+
if ("object" === typeof navigation) {
17749+
var isCancelled = !1,
17750+
pendingResolve = null;
17751+
navigation.addEventListener("navigate", handleNavigate);
17752+
navigation.addEventListener("navigatesuccess", handleNavigateComplete);
17753+
navigation.addEventListener("navigateerror", handleNavigateComplete);
17754+
setTimeout(startFakeNavigation, 100);
17755+
return function () {
17756+
isCancelled = !0;
17757+
navigation.removeEventListener("navigate", handleNavigate);
17758+
navigation.removeEventListener("navigatesuccess", handleNavigateComplete);
17759+
navigation.removeEventListener("navigateerror", handleNavigateComplete);
17760+
null !== pendingResolve && (pendingResolve(), (pendingResolve = null));
17761+
};
17762+
}
1772017763
}
1772117764
function ReactDOMRoot(internalRoot) {
1772217765
this._internalRoot = internalRoot;
@@ -17761,14 +17804,14 @@ ReactDOMHydrationRoot.prototype.unstable_scheduleHydration = function (target) {
1776117804
};
1776217805
var isomorphicReactPackageVersion$jscomp$inline_2118 = React.version;
1776317806
if (
17764-
"19.2.0-native-fb-b480865d-20250513" !==
17807+
"19.2.0-native-fb-59440424-20250513" !==
1776517808
isomorphicReactPackageVersion$jscomp$inline_2118
1776617809
)
1776717810
throw Error(
1776817811
formatProdErrorMessage(
1776917812
527,
1777017813
isomorphicReactPackageVersion$jscomp$inline_2118,
17771-
"19.2.0-native-fb-b480865d-20250513"
17814+
"19.2.0-native-fb-59440424-20250513"
1777217815
)
1777317816
);
1777417817
ReactDOMSharedInternals.findDOMNode = function (componentOrElement) {
@@ -17790,10 +17833,10 @@ ReactDOMSharedInternals.findDOMNode = function (componentOrElement) {
1779017833
};
1779117834
var internals$jscomp$inline_2125 = {
1779217835
bundleType: 0,
17793-
version: "19.2.0-native-fb-b480865d-20250513",
17836+
version: "19.2.0-native-fb-59440424-20250513",
1779417837
rendererPackageName: "react-dom",
1779517838
currentDispatcherRef: ReactSharedInternals,
17796-
reconcilerVersion: "19.2.0-native-fb-b480865d-20250513",
17839+
reconcilerVersion: "19.2.0-native-fb-59440424-20250513",
1779717840
getLaneLabelMap: function () {
1779817841
for (
1779917842
var map = new Map(), lane = 1, index$313 = 0;
@@ -17906,4 +17949,4 @@ exports.hydrateRoot = function (container, initialChildren, options) {
1790617949
listenToAllSupportedEvents(container);
1790717950
return new ReactDOMHydrationRoot(initialChildren);
1790817951
};
17909-
exports.version = "19.2.0-native-fb-b480865d-20250513";
17952+
exports.version = "19.2.0-native-fb-59440424-20250513";

0 commit comments

Comments
 (0)