one 1.19.6 → 1.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/link/Redirect.cjs +9 -4
- package/dist/cjs/link/Redirect.native.js +9 -4
- package/dist/cjs/link/Redirect.native.js.map +1 -1
- package/dist/cjs/link/Redirect.test.cjs +94 -0
- package/dist/cjs/link/Redirect.test.native.js +106 -0
- package/dist/cjs/link/Redirect.test.native.js.map +1 -0
- package/dist/esm/link/Redirect.mjs +9 -4
- package/dist/esm/link/Redirect.mjs.map +1 -1
- package/dist/esm/link/Redirect.native.js +9 -4
- package/dist/esm/link/Redirect.native.js.map +1 -1
- package/dist/esm/link/Redirect.test.mjs +71 -0
- package/dist/esm/link/Redirect.test.mjs.map +1 -0
- package/dist/esm/link/Redirect.test.native.js +80 -0
- package/dist/esm/link/Redirect.test.native.js.map +1 -0
- package/package.json +9 -9
- package/src/link/Redirect.test.tsx +85 -0
- package/src/link/Redirect.tsx +29 -5
- package/types/link/Redirect.d.ts.map +1 -1
- package/types/link/Redirect.test.d.ts +2 -0
- package/types/link/Redirect.test.d.ts.map +1 -0
|
@@ -28,6 +28,7 @@ module.exports = __toCommonJS(Redirect_exports);
|
|
|
28
28
|
var import_react = require("react");
|
|
29
29
|
var import_hooks = require("../hooks.cjs");
|
|
30
30
|
var import_useFocusEffect = require("../useFocusEffect.cjs");
|
|
31
|
+
const redirectsInFlight = /* @__PURE__ */new Set();
|
|
31
32
|
function Redirect({
|
|
32
33
|
href
|
|
33
34
|
}) {
|
|
@@ -35,12 +36,16 @@ function Redirect({
|
|
|
35
36
|
const hasRedirected = (0, import_react.useRef)(false);
|
|
36
37
|
(0, import_useFocusEffect.useFocusEffect)(() => {
|
|
37
38
|
if (hasRedirected.current) return;
|
|
39
|
+
const key = typeof href === "string" ? href : JSON.stringify(href);
|
|
40
|
+
if (redirectsInFlight.has(key)) return;
|
|
38
41
|
hasRedirected.current = true;
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
+
redirectsInFlight.add(key);
|
|
43
|
+
Promise.resolve(router.replace(href)).catch(error => {
|
|
44
|
+
hasRedirected.current = false;
|
|
42
45
|
console.error(error);
|
|
43
|
-
}
|
|
46
|
+
}).finally(() => {
|
|
47
|
+
redirectsInFlight.delete(key);
|
|
48
|
+
});
|
|
44
49
|
}, [href]);
|
|
45
50
|
return null;
|
|
46
51
|
}
|
|
@@ -30,6 +30,7 @@ module.exports = __toCommonJS(Redirect_exports);
|
|
|
30
30
|
var import_react = require("react");
|
|
31
31
|
var import_hooks = require("../hooks.native.js");
|
|
32
32
|
var import_useFocusEffect = require("../useFocusEffect.native.js");
|
|
33
|
+
var redirectsInFlight = /* @__PURE__ */new Set();
|
|
33
34
|
function Redirect(param) {
|
|
34
35
|
var {
|
|
35
36
|
href
|
|
@@ -38,12 +39,16 @@ function Redirect(param) {
|
|
|
38
39
|
var hasRedirected = (0, import_react.useRef)(false);
|
|
39
40
|
(0, import_useFocusEffect.useFocusEffect)(function () {
|
|
40
41
|
if (hasRedirected.current) return;
|
|
42
|
+
var key = typeof href === "string" ? href : JSON.stringify(href);
|
|
43
|
+
if (redirectsInFlight.has(key)) return;
|
|
41
44
|
hasRedirected.current = true;
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
+
redirectsInFlight.add(key);
|
|
46
|
+
Promise.resolve(router.replace(href)).catch(function (error) {
|
|
47
|
+
hasRedirected.current = false;
|
|
45
48
|
console.error(error);
|
|
46
|
-
}
|
|
49
|
+
}).finally(function () {
|
|
50
|
+
redirectsInFlight.delete(key);
|
|
51
|
+
});
|
|
47
52
|
}, [href]);
|
|
48
53
|
return null;
|
|
49
54
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["__toCommonJS","mod","__copyProps","__defProp","value","Redirect_exports","__export","Redirect","module","exports","import_react","require","import_hooks","import_useFocusEffect","param","href","router","useRouter","hasRedirected","useRef","useFocusEffect","current","replace","error","console"],"sources":["../../../src/link/Redirect.tsx"],"sourcesContent":[null],"mappings":";;;;;;;;;;;;;;;;;;;;;AAAA,IAAAA,YAAA,GAAAC,GAAA,IAAAC,WAAA,CAAAC,SAAA;EAAAC,KAAA;AAAA,IAAAH,GAAA;AAAA,IAAAI,gBAAA;AAAAC,QAAA,CAAAD,gBAAA;EAAAE,QAAA,EAAAA,CAAA,KAAAA;AAAA;AAAAC,MAAA,CAAAC,OAAA,GAAAT,YAAuB,CAAAK,gBAAA;AACvB,IAAAK,YAAA,GAA0BC,OAAA;AAE1B,IAAAC,YAAA,GAAAD,OAAA,qBAA+B;
|
|
1
|
+
{"version":3,"names":["__toCommonJS","mod","__copyProps","__defProp","value","Redirect_exports","__export","Redirect","module","exports","import_react","require","import_hooks","import_useFocusEffect","redirectsInFlight","Set","param","href","router","useRouter","hasRedirected","useRef","useFocusEffect","current","key","JSON","stringify","has","add","Promise","resolve","replace","catch","error","console","finally","delete"],"sources":["../../../src/link/Redirect.tsx"],"sourcesContent":[null],"mappings":";;;;;;;;;;;;;;;;;;;;;AAAA,IAAAA,YAAA,GAAAC,GAAA,IAAAC,WAAA,CAAAC,SAAA;EAAAC,KAAA;AAAA,IAAAH,GAAA;AAAA,IAAAI,gBAAA;AAAAC,QAAA,CAAAD,gBAAA;EAAAE,QAAA,EAAAA,CAAA,KAAAA;AAAA;AAAAC,MAAA,CAAAC,OAAA,GAAAT,YAAuB,CAAAK,gBAAA;AACvB,IAAAK,YAAA,GAA0BC,OAAA;AAE1B,IAAAC,YAAA,GAAAD,OAAA,qBAA+B;AAa/B,IAAAE,qBAAM,GAAoBF,OAAA,8BAAgB;AAInC,IAAAG,iBAAoB,kBAAkC,IAAAC,GAAA;AAC3D,SAAMR,SAAAS,KAAS;EACf;IAAMC;EAAA,IAAAD,KAAA;EAEN,IAAAE,MAAA,OAAAN,YAAA,CAAAO,SAAA,GAAe;EACb,IAAAC,aAAI,GAAc,IAAAV,YAAS,CAAAW,MAAA;EAE3B,IAAAR,qBAAmB,CAAAS,cAAS,EAAW,YAAY;IAEnD,IAAIF,aAAA,CAAAG,OAAkB,EAAI;IAE1B,IAAAC,GAAA,UAAcP,IAAA,KAAU,WAAAA,IAAA,GAAAQ,IAAA,CAAAC,SAAA,CAAAT,IAAA;IACxB,IAAAH,iBAAkB,CAAIa,GAAG,CAAAH,GAAA;IAEzBJ,aAAQ,CAAAG,OAAQ,GAAO;IAGnBT,iBAAc,CAAAc,GAAA,CAAAJ,GAAA,CAAU;IACxBK,OAAA,CAAAC,OAAQ,CAAMZ,MAAK,CAAAa,OAAA,CAAAd,IAAA,GAAAe,KAAA,WAAAC,KAAA;MACpBb,aACQ,CAAAG,OAAM;MACbW,OAAA,CAAAD,KAAA,CAAAA,KAAkB;IACpB,CAAC,EAAAE,OAAA;MACDrB,iBAAK,CAAAsB,MAAA,CAAAZ,GAAA;IAET;EACF,I","ignoreList":[]}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
var __create = Object.create;
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __copyProps = (to, from, except, desc) => {
|
|
8
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
9
|
+
for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
10
|
+
get: () => from[key],
|
|
11
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
return to;
|
|
15
|
+
};
|
|
16
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
17
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
18
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
19
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
20
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
21
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
22
|
+
value: mod,
|
|
23
|
+
enumerable: true
|
|
24
|
+
}) : target, mod));
|
|
25
|
+
var import_vitest = require("vitest");
|
|
26
|
+
var import_react = __toESM(require("react"), 1);
|
|
27
|
+
var import_react_test_renderer = __toESM(require("react-test-renderer"), 1);
|
|
28
|
+
var import_Redirect = require("./Redirect.cjs");
|
|
29
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
30
|
+
import_vitest.vi.mock("../useFocusEffect", () => ({
|
|
31
|
+
useFocusEffect: (effect, deps) => import_react.default.useEffect(effect, deps)
|
|
32
|
+
}));
|
|
33
|
+
const replaceCalls = [];
|
|
34
|
+
let resolvePending;
|
|
35
|
+
import_vitest.vi.mock("../hooks", () => ({
|
|
36
|
+
useRouter: () => ({
|
|
37
|
+
replace: href => {
|
|
38
|
+
replaceCalls.push(href);
|
|
39
|
+
return new Promise(resolve => {
|
|
40
|
+
resolvePending = resolve;
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
})
|
|
44
|
+
}));
|
|
45
|
+
async function flush() {
|
|
46
|
+
await (0, import_react_test_renderer.act)(async () => {
|
|
47
|
+
await Promise.resolve();
|
|
48
|
+
await Promise.resolve();
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
(0, import_vitest.describe)("Redirect", () => {
|
|
52
|
+
(0, import_vitest.it)("fires replace once across the mount/unmount churn of an auth-state settle", async () => {
|
|
53
|
+
replaceCalls.length = 0;
|
|
54
|
+
for (let i = 0; i < 8; i++) {
|
|
55
|
+
let r;
|
|
56
|
+
(0, import_react_test_renderer.act)(() => {
|
|
57
|
+
r = import_react_test_renderer.default.create(/* @__PURE__ */(0, import_jsx_runtime.jsx)(import_Redirect.Redirect, {
|
|
58
|
+
href: "/home"
|
|
59
|
+
}));
|
|
60
|
+
});
|
|
61
|
+
(0, import_react_test_renderer.act)(() => {
|
|
62
|
+
r.unmount();
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
(0, import_vitest.expect)(replaceCalls).toEqual(["/home"]);
|
|
66
|
+
resolvePending?.();
|
|
67
|
+
await flush();
|
|
68
|
+
(0, import_react_test_renderer.act)(() => {
|
|
69
|
+
import_react_test_renderer.default.create(/* @__PURE__ */(0, import_jsx_runtime.jsx)(import_Redirect.Redirect, {
|
|
70
|
+
href: "/home"
|
|
71
|
+
}));
|
|
72
|
+
});
|
|
73
|
+
(0, import_vitest.expect)(replaceCalls).toEqual(["/home", "/home"]);
|
|
74
|
+
resolvePending?.();
|
|
75
|
+
await flush();
|
|
76
|
+
});
|
|
77
|
+
(0, import_vitest.it)("does not re-fire within a single mounted instance", async () => {
|
|
78
|
+
replaceCalls.length = 0;
|
|
79
|
+
let r;
|
|
80
|
+
(0, import_react_test_renderer.act)(() => {
|
|
81
|
+
r = import_react_test_renderer.default.create(/* @__PURE__ */(0, import_jsx_runtime.jsx)(import_Redirect.Redirect, {
|
|
82
|
+
href: "/login"
|
|
83
|
+
}));
|
|
84
|
+
});
|
|
85
|
+
(0, import_react_test_renderer.act)(() => {
|
|
86
|
+
r.update(/* @__PURE__ */(0, import_jsx_runtime.jsx)(import_Redirect.Redirect, {
|
|
87
|
+
href: "/login"
|
|
88
|
+
}));
|
|
89
|
+
});
|
|
90
|
+
(0, import_vitest.expect)(replaceCalls).toEqual(["/login"]);
|
|
91
|
+
resolvePending?.();
|
|
92
|
+
await flush();
|
|
93
|
+
});
|
|
94
|
+
});
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
|
+
for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
12
|
+
get: () => from[key],
|
|
13
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
19
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
20
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
21
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
22
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
23
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
24
|
+
value: mod,
|
|
25
|
+
enumerable: true
|
|
26
|
+
}) : target, mod));
|
|
27
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
28
|
+
var import_vitest = require("vitest");
|
|
29
|
+
var import_react = __toESM(require("react"), 1);
|
|
30
|
+
var import_react_test_renderer = __toESM(require("react-test-renderer"), 1);
|
|
31
|
+
var import_Redirect = require("./Redirect.native.js");
|
|
32
|
+
import_vitest.vi.mock("../useFocusEffect", function () {
|
|
33
|
+
return {
|
|
34
|
+
useFocusEffect: function useFocusEffect(effect, deps) {
|
|
35
|
+
return import_react.default.useEffect(effect, deps);
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
});
|
|
39
|
+
var replaceCalls = [];
|
|
40
|
+
var resolvePending;
|
|
41
|
+
import_vitest.vi.mock("../hooks", function () {
|
|
42
|
+
return {
|
|
43
|
+
useRouter: function useRouter() {
|
|
44
|
+
return {
|
|
45
|
+
replace: function replace(href) {
|
|
46
|
+
replaceCalls.push(href);
|
|
47
|
+
return new Promise(function (resolve) {
|
|
48
|
+
resolvePending = resolve;
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
});
|
|
55
|
+
async function flush() {
|
|
56
|
+
await (0, import_react_test_renderer.act)(async function () {
|
|
57
|
+
await Promise.resolve();
|
|
58
|
+
await Promise.resolve();
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
(0, import_vitest.describe)("Redirect", function () {
|
|
62
|
+
(0, import_vitest.it)("fires replace once across the mount/unmount churn of an auth-state settle", async function () {
|
|
63
|
+
var _loop = function (i2) {
|
|
64
|
+
var r = void 0;
|
|
65
|
+
(0, import_react_test_renderer.act)(function () {
|
|
66
|
+
r = import_react_test_renderer.default.create(/* @__PURE__ */(0, import_jsx_runtime.jsx)(import_Redirect.Redirect, {
|
|
67
|
+
href: "/home"
|
|
68
|
+
}));
|
|
69
|
+
});
|
|
70
|
+
(0, import_react_test_renderer.act)(function () {
|
|
71
|
+
r.unmount();
|
|
72
|
+
});
|
|
73
|
+
};
|
|
74
|
+
replaceCalls.length = 0;
|
|
75
|
+
for (var i = 0; i < 8; i++) _loop(i);
|
|
76
|
+
(0, import_vitest.expect)(replaceCalls).toEqual(["/home"]);
|
|
77
|
+
resolvePending === null || resolvePending === void 0 ? void 0 : resolvePending();
|
|
78
|
+
await flush();
|
|
79
|
+
(0, import_react_test_renderer.act)(function () {
|
|
80
|
+
import_react_test_renderer.default.create(/* @__PURE__ */(0, import_jsx_runtime.jsx)(import_Redirect.Redirect, {
|
|
81
|
+
href: "/home"
|
|
82
|
+
}));
|
|
83
|
+
});
|
|
84
|
+
(0, import_vitest.expect)(replaceCalls).toEqual(["/home", "/home"]);
|
|
85
|
+
resolvePending === null || resolvePending === void 0 ? void 0 : resolvePending();
|
|
86
|
+
await flush();
|
|
87
|
+
});
|
|
88
|
+
(0, import_vitest.it)("does not re-fire within a single mounted instance", async function () {
|
|
89
|
+
replaceCalls.length = 0;
|
|
90
|
+
var r;
|
|
91
|
+
(0, import_react_test_renderer.act)(function () {
|
|
92
|
+
r = import_react_test_renderer.default.create(/* @__PURE__ */(0, import_jsx_runtime.jsx)(import_Redirect.Redirect, {
|
|
93
|
+
href: "/login"
|
|
94
|
+
}));
|
|
95
|
+
});
|
|
96
|
+
(0, import_react_test_renderer.act)(function () {
|
|
97
|
+
r.update(/* @__PURE__ */(0, import_jsx_runtime.jsx)(import_Redirect.Redirect, {
|
|
98
|
+
href: "/login"
|
|
99
|
+
}));
|
|
100
|
+
});
|
|
101
|
+
(0, import_vitest.expect)(replaceCalls).toEqual(["/login"]);
|
|
102
|
+
resolvePending === null || resolvePending === void 0 ? void 0 : resolvePending();
|
|
103
|
+
await flush();
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
//# sourceMappingURL=Redirect.test.native.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["import_jsx_runtime","require","import_vitest","import_react","__toESM","import_react_test_renderer","import_Redirect","vi","mock","useFocusEffect","effect","deps","default","useEffect","replaceCalls","resolvePending","useRouter","replace","href","push","Promise","resolve","flush","act","describe","it","_loop","i2","r","create","jsx","Redirect","unmount","length","i","expect","toEqual"],"sources":["../../../src/link/Redirect.test.tsx"],"sourcesContent":[null],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;gBAAA;AACA,IAAAA,kBAAkB,GAAAC,OAAA;AAClB,IAAAC,aAAA,GAAAD,OAAA,SAAkC;AAuBlC,IAAAE,YAAA,GAAAC,OAAyB,CAAAH,OAAA;AAmBO,IAAAI,0BAAA,GAAAD,OAAA,CAAAH,OAAA;AAvChC,IAAAK,eAAG,GAAKL,OAAA,wBAAqB;AAAOC,aAClC,CAAAK,EAAA,CAAAC,IAAiB,oBACf;EACF;IAEFC,cAAM,WAA0BA,eAAAC,MAAA,EAAAC,IAAA;MAC5B,OAAAR,YAAA,CAAAS,OAAA,CAAAC,SAAA,CAAAH,MAAA,EAAAC,IAAA;IAEJ;EACE;AAAkB;AAEd,IAAAG,YAAA,GAAa;AAEb,IAAAC,cAAW;AACTb,aAAA,CAAAK,EAAA,CAAAC,IAAA,WAAiB;EAAA,OAClB;IACHQ,SAAA,WAAAA,UAAA;MACF;QACAC,OAAA,WAAAA,QAAAC,IAAA;UAIFJ,YAAe,CAAQK,IAAA,CAAAD,IAAA;UAErB,OAAM,IAAAE,OAAA,WAAAC,OAAI;YACFN,cAAgB,GAAAM,OAAA;UAChB;QACP;MACH;IAEA;EACE;AACE;AAIA,eAASC,KAAOA,CAAA;EACd,QAAI,EAAAjB,0BAAA,CAAAkB,GAAA;IACJ,MAAAH,OAAA,CAAAC,OAAA;IACE,MAAAD,OAAI,CAAAC,OAAA;EAA6C;AAEnD;AACE,IAAAnB,aAAG,CAAQsB,QAAA;EAAA,IACbtB,aAAC,CAAAuB,EAAA;IACH,IAAAC,KAAA,YAAAA,CAAAC,EAAA;MAEA,IAAAC,CAAA;MAGA,IAAAvB,0BAAiB,CAAAkB,GAAA;QACjBK,CAAA,GAAMvB,0BAAM,CAAAO,OAAA,CAAAiB,MAAA,oBAAA7B,kBAAA,CAAA8B,GAAA,EAAAxB,eAAA,CAAAyB,QAAA;UAGZb,IAAA;QACE;MACD;MACD,IAAAb,0BAAO,CAAAkB,GAAA,EAAY,YAAW;QAE9BK,CAAA,CAAAI,OAAA;MACA;IACD;IAEDlB,YAAA,CAAAmB,MAAA,GAAG;IACD,SAAAC,CAAA,GAAa,GAAAA,CAAA,MAASA,CAAA,IAAAR,KAAA,CAAAQ,CAAA;IAEtB,IAAIhC,aAAA,CAAAiC,MAAA,EAAArB,YAAA,EAAAsB,OAAA,EACJ,QACE;IACFrB,cAAC,aAAAA,cAAA,uBAAAA,cAAA;IAED,MAAAO,KAAA;IACE,IAAGjB,0BAAO,CAAAkB,GAAA;MACXlB,0BAAA,CAAAO,OAAA,CAAAiB,MAAA,oBAAA7B,kBAAA,CAAA8B,GAAA,EAAAxB,eAAA,CAAAyB,QAAA;QAEDb,IAAA;MAEA;IACA;IACD,IAAAhB,aAAA,CAAAiC,MAAA,EAAArB,YAAA,EAAAsB,OAAA,EACF,S","ignoreList":[]}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { useRef } from "react";
|
|
2
2
|
import { useRouter } from "../hooks.mjs";
|
|
3
3
|
import { useFocusEffect } from "../useFocusEffect.mjs";
|
|
4
|
+
const redirectsInFlight = /* @__PURE__ */new Set();
|
|
4
5
|
function Redirect({
|
|
5
6
|
href
|
|
6
7
|
}) {
|
|
@@ -8,12 +9,16 @@ function Redirect({
|
|
|
8
9
|
const hasRedirected = useRef(false);
|
|
9
10
|
useFocusEffect(() => {
|
|
10
11
|
if (hasRedirected.current) return;
|
|
12
|
+
const key = typeof href === "string" ? href : JSON.stringify(href);
|
|
13
|
+
if (redirectsInFlight.has(key)) return;
|
|
11
14
|
hasRedirected.current = true;
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
+
redirectsInFlight.add(key);
|
|
16
|
+
Promise.resolve(router.replace(href)).catch(error => {
|
|
17
|
+
hasRedirected.current = false;
|
|
15
18
|
console.error(error);
|
|
16
|
-
}
|
|
19
|
+
}).finally(() => {
|
|
20
|
+
redirectsInFlight.delete(key);
|
|
21
|
+
});
|
|
17
22
|
}, [href]);
|
|
18
23
|
return null;
|
|
19
24
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["useRef","useRouter","useFocusEffect","Redirect","href","router","hasRedirected","current","replace","error","console"],"sources":["../../../src/link/Redirect.tsx"],"sourcesContent":[null],"mappings":"AAAA,SAASA,MAAA,QAAc;AACvB,SAASC,SAAA,QAAiB;AAE1B,SAASC,cAAA,QAAsB;
|
|
1
|
+
{"version":3,"names":["useRef","useRouter","useFocusEffect","redirectsInFlight","Set","Redirect","href","router","hasRedirected","current","key","JSON","stringify","has","add","Promise","resolve","replace","catch","error","console","finally","delete"],"sources":["../../../src/link/Redirect.tsx"],"sourcesContent":[null],"mappings":"AAAA,SAASA,MAAA,QAAc;AACvB,SAASC,SAAA,QAAiB;AAE1B,SAASC,cAAA,QAAsB;AAa/B,MAAMC,iBAAA,GAAoB,mBAAIC,GAAA,CAAY;AAInC,SAASC,SAAS;EAAEC;AAAK,GAA6B;EAC3D,MAAMC,MAAA,GAASN,SAAA,CAAU;EACzB,MAAMO,aAAA,GAAgBR,MAAA,CAAO,KAAK;EAElCE,cAAA,CAAe,MAAM;IACnB,IAAIM,aAAA,CAAcC,OAAA,EAAS;IAE3B,MAAMC,GAAA,GAAM,OAAOJ,IAAA,KAAS,WAAWA,IAAA,GAAOK,IAAA,CAAKC,SAAA,CAAUN,IAAI;IAEjE,IAAIH,iBAAA,CAAkBU,GAAA,CAAIH,GAAG,GAAG;IAEhCF,aAAA,CAAcC,OAAA,GAAU;IACxBN,iBAAA,CAAkBW,GAAA,CAAIJ,GAAG;IAEzBK,OAAA,CAAQC,OAAA,CAAQT,MAAA,CAAOU,OAAA,CAAQX,IAAI,CAAC,EACjCY,KAAA,CAAOC,KAAA,IAAU;MAEhBX,aAAA,CAAcC,OAAA,GAAU;MACxBW,OAAA,CAAQD,KAAA,CAAMA,KAAK;IACrB,CAAC,EACAE,OAAA,CAAQ,MAAM;MACblB,iBAAA,CAAkBmB,MAAA,CAAOZ,GAAG;IAC9B,CAAC;EACL,GAAG,CAACJ,IAAI,CAAC;EAET,OAAO;AACT","ignoreList":[]}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { useRef } from "react";
|
|
2
2
|
import { useRouter } from "../hooks.native.js";
|
|
3
3
|
import { useFocusEffect } from "../useFocusEffect.native.js";
|
|
4
|
+
var redirectsInFlight = /* @__PURE__ */new Set();
|
|
4
5
|
function Redirect(param) {
|
|
5
6
|
var {
|
|
6
7
|
href
|
|
@@ -9,12 +10,16 @@ function Redirect(param) {
|
|
|
9
10
|
var hasRedirected = useRef(false);
|
|
10
11
|
useFocusEffect(function () {
|
|
11
12
|
if (hasRedirected.current) return;
|
|
13
|
+
var key = typeof href === "string" ? href : JSON.stringify(href);
|
|
14
|
+
if (redirectsInFlight.has(key)) return;
|
|
12
15
|
hasRedirected.current = true;
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
+
redirectsInFlight.add(key);
|
|
17
|
+
Promise.resolve(router.replace(href)).catch(function (error) {
|
|
18
|
+
hasRedirected.current = false;
|
|
16
19
|
console.error(error);
|
|
17
|
-
}
|
|
20
|
+
}).finally(function () {
|
|
21
|
+
redirectsInFlight.delete(key);
|
|
22
|
+
});
|
|
18
23
|
}, [href]);
|
|
19
24
|
return null;
|
|
20
25
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["useRef","useRouter","useFocusEffect","Redirect","param","href","router","hasRedirected","current","replace","error","console"],"sources":["../../../src/link/Redirect.tsx"],"sourcesContent":[null],"mappings":"AAAA,SAASA,MAAA,QAAc;AACvB,SAASC,SAAA,QAAiB;AAE1B,SAASC,cAAA,QAAsB;
|
|
1
|
+
{"version":3,"names":["useRef","useRouter","useFocusEffect","redirectsInFlight","Set","Redirect","param","href","router","hasRedirected","current","key","JSON","stringify","has","add","Promise","resolve","replace","catch","error","console","finally","delete"],"sources":["../../../src/link/Redirect.tsx"],"sourcesContent":[null],"mappings":"AAAA,SAASA,MAAA,QAAc;AACvB,SAASC,SAAA,QAAiB;AAE1B,SAASC,cAAA,QAAsB;AAa/B,IAAAC,iBAAM,kBAAoB,IAAAC,GAAI;AAIvB,SAASC,SAASC,KAAE,EAAK;EAC9B;IAAMC;EAAA,IAASD,KAAA;EACf,IAAAE,MAAM,GAAAP,SAAgB;EAEtB,IAAAQ,aAAe,GAAAT,MAAM;EACnBE,cAAI,aAAc;IAElB,IAAAO,aAAY,CAAAC,OAAO;IAEnB,IAAIC,GAAA,UAAAJ,IAAA,KAAsB,QAAM,GAAAA,IAAA,GAAAK,IAAA,CAAAC,SAAA,CAAAN,IAAA;IAEhC,IAAAJ,iBAAc,CAAAW,GAAU,CAAAH,GAAA;IACxBF,aAAA,CAAAC,OAAkB,GAAI,IAAG;IAEzBP,iBAAgB,CAAAY,GAAA,CAAAJ,GAAO;IAGnBK,OAAA,CAAAC,OAAA,CAAcT,MAAA,CAAAU,OAAU,CAAAX,IAAA,GAAAY,KAAA,WAAAC,KAAA;MACxBX,aAAQ,CAAMC,OAAK;MACpBW,OACA,CAAAD,KAAQ,CAAAA,KAAM;IACb,GAAAE,OAAA,aAAkB;MACnBnB,iBAAA,CAAAoB,MAAA,CAAAZ,GAAA;IACL,CAAG,CAAC;EAEJ,IACFJ,IAAA,C","ignoreList":[]}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from "vitest";
|
|
2
|
+
import React from "react";
|
|
3
|
+
import TestRenderer, { act } from "react-test-renderer";
|
|
4
|
+
import { Redirect } from "./Redirect.mjs";
|
|
5
|
+
import { jsx } from "react/jsx-runtime";
|
|
6
|
+
vi.mock("../useFocusEffect", () => ({
|
|
7
|
+
useFocusEffect: (effect, deps) => React.useEffect(effect, deps)
|
|
8
|
+
}));
|
|
9
|
+
const replaceCalls = [];
|
|
10
|
+
let resolvePending;
|
|
11
|
+
vi.mock("../hooks", () => ({
|
|
12
|
+
useRouter: () => ({
|
|
13
|
+
replace: href => {
|
|
14
|
+
replaceCalls.push(href);
|
|
15
|
+
return new Promise(resolve => {
|
|
16
|
+
resolvePending = resolve;
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
})
|
|
20
|
+
}));
|
|
21
|
+
async function flush() {
|
|
22
|
+
await act(async () => {
|
|
23
|
+
await Promise.resolve();
|
|
24
|
+
await Promise.resolve();
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
describe("Redirect", () => {
|
|
28
|
+
it("fires replace once across the mount/unmount churn of an auth-state settle", async () => {
|
|
29
|
+
replaceCalls.length = 0;
|
|
30
|
+
for (let i = 0; i < 8; i++) {
|
|
31
|
+
let r;
|
|
32
|
+
act(() => {
|
|
33
|
+
r = TestRenderer.create(/* @__PURE__ */jsx(Redirect, {
|
|
34
|
+
href: "/home"
|
|
35
|
+
}));
|
|
36
|
+
});
|
|
37
|
+
act(() => {
|
|
38
|
+
r.unmount();
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
expect(replaceCalls).toEqual(["/home"]);
|
|
42
|
+
resolvePending?.();
|
|
43
|
+
await flush();
|
|
44
|
+
act(() => {
|
|
45
|
+
TestRenderer.create(/* @__PURE__ */jsx(Redirect, {
|
|
46
|
+
href: "/home"
|
|
47
|
+
}));
|
|
48
|
+
});
|
|
49
|
+
expect(replaceCalls).toEqual(["/home", "/home"]);
|
|
50
|
+
resolvePending?.();
|
|
51
|
+
await flush();
|
|
52
|
+
});
|
|
53
|
+
it("does not re-fire within a single mounted instance", async () => {
|
|
54
|
+
replaceCalls.length = 0;
|
|
55
|
+
let r;
|
|
56
|
+
act(() => {
|
|
57
|
+
r = TestRenderer.create(/* @__PURE__ */jsx(Redirect, {
|
|
58
|
+
href: "/login"
|
|
59
|
+
}));
|
|
60
|
+
});
|
|
61
|
+
act(() => {
|
|
62
|
+
r.update(/* @__PURE__ */jsx(Redirect, {
|
|
63
|
+
href: "/login"
|
|
64
|
+
}));
|
|
65
|
+
});
|
|
66
|
+
expect(replaceCalls).toEqual(["/login"]);
|
|
67
|
+
resolvePending?.();
|
|
68
|
+
await flush();
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
//# sourceMappingURL=Redirect.test.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["describe","expect","it","vi","React","TestRenderer","act","Redirect","jsx","mock","useFocusEffect","effect","deps","useEffect","replaceCalls","resolvePending","useRouter","replace","href","push","Promise","resolve","flush","length","i","r","create","unmount","toEqual","update"],"sources":["../../../src/link/Redirect.test.tsx"],"sourcesContent":[null],"mappings":"AAAA,SAASA,QAAA,EAAUC,MAAA,EAAQC,EAAA,EAAIC,EAAA,QAAU;AACzC,OAAOC,KAAA,MAAW;AAClB,OAAOC,YAAA,IAAgBC,GAAA,QAAW;AAuBlC,SAASC,QAAA,QAAgB;AAmBO,SAAAC,GAAA;AAvChCL,EAAA,CAAGM,IAAA,CAAK,qBAAqB,OAAO;EAClCC,cAAA,EAAgBA,CAACC,MAAA,EAAmCC,IAAA,KAClDR,KAAA,CAAMS,SAAA,CAAUF,MAAA,EAAQC,IAAI;AAChC,EAAE;AAEF,MAAME,YAAA,GAAyB,EAAC;AAChC,IAAIC,cAAA;AAEJZ,EAAA,CAAGM,IAAA,CAAK,YAAY,OAAO;EACzBO,SAAA,EAAWA,CAAA,MAAO;IAChBC,OAAA,EAAUC,IAAA,IAAiB;MACzBJ,YAAA,CAAaK,IAAA,CAAKD,IAAI;MAEtB,OAAO,IAAIE,OAAA,CAAeC,OAAA,IAAY;QACpCN,cAAA,GAAiBM,OAAA;MACnB,CAAC;IACH;EACF;AACF,EAAE;AAIF,eAAeC,MAAA,EAAQ;EAErB,MAAMhB,GAAA,CAAI,YAAY;IACpB,MAAMc,OAAA,CAAQC,OAAA,CAAQ;IACtB,MAAMD,OAAA,CAAQC,OAAA,CAAQ;EACxB,CAAC;AACH;AAEArB,QAAA,CAAS,YAAY,MAAM;EACzBE,EAAA,CAAG,6EAA6E,YAAY;IAC1FY,YAAA,CAAaS,MAAA,GAAS;IAItB,SAASC,CAAA,GAAI,GAAGA,CAAA,GAAI,GAAGA,CAAA,IAAK;MAC1B,IAAIC,CAAA;MACJnB,GAAA,CAAI,MAAM;QACRmB,CAAA,GAAIpB,YAAA,CAAaqB,MAAA,CAAO,eAAAlB,GAAA,CAACD,QAAA;UAASW,IAAA,EAAK;QAAA,CAAQ,CAAE;MACnD,CAAC;MACDZ,GAAA,CAAI,MAAM;QACRmB,CAAA,CAAGE,OAAA,CAAQ;MACb,CAAC;IACH;IAEA1B,MAAA,CAAOa,YAAY,EAAEc,OAAA,CAAQ,CAAC,OAAO,CAAC;IAGtCb,cAAA,GAAiB;IACjB,MAAMO,KAAA,CAAM;IAGZhB,GAAA,CAAI,MAAM;MACRD,YAAA,CAAaqB,MAAA,CAAO,eAAAlB,GAAA,CAACD,QAAA;QAASW,IAAA,EAAK;MAAA,CAAQ,CAAE;IAC/C,CAAC;IACDjB,MAAA,CAAOa,YAAY,EAAEc,OAAA,CAAQ,CAAC,SAAS,OAAO,CAAC;IAE/Cb,cAAA,GAAiB;IACjB,MAAMO,KAAA,CAAM;EACd,CAAC;EAEDpB,EAAA,CAAG,qDAAqD,YAAY;IAClEY,YAAA,CAAaS,MAAA,GAAS;IAEtB,IAAIE,CAAA;IACJnB,GAAA,CAAI,MAAM;MACRmB,CAAA,GAAIpB,YAAA,CAAaqB,MAAA,CAAO,eAAAlB,GAAA,CAACD,QAAA;QAASW,IAAA,EAAK;MAAA,CAAS,CAAE;IACpD,CAAC;IAEDZ,GAAA,CAAI,MAAM;MACRmB,CAAA,CAAGI,MAAA,CAAO,eAAArB,GAAA,CAACD,QAAA;QAASW,IAAA,EAAK;MAAA,CAAS,CAAE;IACtC,CAAC;IAEDjB,MAAA,CAAOa,YAAY,EAAEc,OAAA,CAAQ,CAAC,QAAQ,CAAC;IAEvCb,cAAA,GAAiB;IACjB,MAAMO,KAAA,CAAM;EACd,CAAC;AACH,CAAC","ignoreList":[]}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { describe, expect, it, vi } from "vitest";
|
|
3
|
+
import React from "react";
|
|
4
|
+
import TestRenderer, { act } from "react-test-renderer";
|
|
5
|
+
import { Redirect } from "./Redirect.native.js";
|
|
6
|
+
vi.mock("../useFocusEffect", function () {
|
|
7
|
+
return {
|
|
8
|
+
useFocusEffect: function useFocusEffect(effect, deps) {
|
|
9
|
+
return React.useEffect(effect, deps);
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
});
|
|
13
|
+
var replaceCalls = [];
|
|
14
|
+
var resolvePending;
|
|
15
|
+
vi.mock("../hooks", function () {
|
|
16
|
+
return {
|
|
17
|
+
useRouter: function useRouter() {
|
|
18
|
+
return {
|
|
19
|
+
replace: function replace(href) {
|
|
20
|
+
replaceCalls.push(href);
|
|
21
|
+
return new Promise(function (resolve) {
|
|
22
|
+
resolvePending = resolve;
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
});
|
|
29
|
+
async function flush() {
|
|
30
|
+
await act(async function () {
|
|
31
|
+
await Promise.resolve();
|
|
32
|
+
await Promise.resolve();
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
describe("Redirect", function () {
|
|
36
|
+
it("fires replace once across the mount/unmount churn of an auth-state settle", async function () {
|
|
37
|
+
var _loop = function (i2) {
|
|
38
|
+
var r = void 0;
|
|
39
|
+
act(function () {
|
|
40
|
+
r = TestRenderer.create(/* @__PURE__ */_jsx(Redirect, {
|
|
41
|
+
href: "/home"
|
|
42
|
+
}));
|
|
43
|
+
});
|
|
44
|
+
act(function () {
|
|
45
|
+
r.unmount();
|
|
46
|
+
});
|
|
47
|
+
};
|
|
48
|
+
replaceCalls.length = 0;
|
|
49
|
+
for (var i = 0; i < 8; i++) _loop(i);
|
|
50
|
+
expect(replaceCalls).toEqual(["/home"]);
|
|
51
|
+
resolvePending === null || resolvePending === void 0 ? void 0 : resolvePending();
|
|
52
|
+
await flush();
|
|
53
|
+
act(function () {
|
|
54
|
+
TestRenderer.create(/* @__PURE__ */_jsx(Redirect, {
|
|
55
|
+
href: "/home"
|
|
56
|
+
}));
|
|
57
|
+
});
|
|
58
|
+
expect(replaceCalls).toEqual(["/home", "/home"]);
|
|
59
|
+
resolvePending === null || resolvePending === void 0 ? void 0 : resolvePending();
|
|
60
|
+
await flush();
|
|
61
|
+
});
|
|
62
|
+
it("does not re-fire within a single mounted instance", async function () {
|
|
63
|
+
replaceCalls.length = 0;
|
|
64
|
+
var r;
|
|
65
|
+
act(function () {
|
|
66
|
+
r = TestRenderer.create(/* @__PURE__ */_jsx(Redirect, {
|
|
67
|
+
href: "/login"
|
|
68
|
+
}));
|
|
69
|
+
});
|
|
70
|
+
act(function () {
|
|
71
|
+
r.update(/* @__PURE__ */_jsx(Redirect, {
|
|
72
|
+
href: "/login"
|
|
73
|
+
}));
|
|
74
|
+
});
|
|
75
|
+
expect(replaceCalls).toEqual(["/login"]);
|
|
76
|
+
resolvePending === null || resolvePending === void 0 ? void 0 : resolvePending();
|
|
77
|
+
await flush();
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
//# sourceMappingURL=Redirect.test.native.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["jsx","_jsx","describe","expect","it","vi","React","TestRenderer","act","Redirect","mock","useFocusEffect","effect","deps","useEffect","replaceCalls","resolvePending","useRouter","replace","href","push","Promise","resolve","flush","_loop","i2","r","create","unmount","length","i","toEqual"],"sources":["../../../src/link/Redirect.test.tsx"],"sourcesContent":[null],"mappings":"AAAA,SAASA,GAAA,IAAAC,IAAU,QAAQ,mBAAc;AACzC,SAAOC,QAAA,EAAWC,MAAA,EAAAC,EAAA,EAAAC,EAAA;AAClB,OAAOC,KAAA,aAAgB;AAuBvB,OAAAC,YAAS,IAAAC,GAAgB;AAmBO,SAAAC,QAAA;AAvChCJ,EAAA,CAAGK,IAAA,CAAK,qBAAqB,YAAO;EAClC;IAEAC,cAAA,WAAAA,eAAAC,MAAA,EAAAC,IAAA;MAEI,OAAAP,KAAA,CAAAQ,SAA0B,CAAAF,MAAA,EAAAC,IAAA;IAC5B;EAEJ,CAAG;AAAwB,EACzB;AAAkB,IAChBE,YAAU;AACR,IAAAC,cAAA;AAEAX,EAAA,CAAAK,IAAA,WAAW,cAAe;EACxB;IAAiBO,SAClB,WAAAA,UAAA;MACH;QACFC,OAAA,WAAAA,QAAAC,IAAA;UACAJ,YAAA,CAAAK,IAAA,CAAAD,IAAA;UAIF,OAAe,IAAAE,OAAQ,WAAAC,OAAA;YAEXN,cAAY,GAAAM,OAAA;UACd;QACN;MACD;IACH;EAEA;AACE;AACE,eAAAC,KAAaA,CAAA;EAIb,MAAAf,GAAA,mBAAuB;IACrB,MAAIa,OAAA,CAAAC,OAAA;IACJ,MAAID,OAAM,CAAAC,OAAA;EACR;AAAiD;AAEnDpB,QAAA,WAAU;EACRE,EAAA,4EAAW;IAAA,IACZoB,KAAA,YAAAA,CAAAC,EAAA;MACH,IAAAC,CAAA;MAEAlB,GAAA,aAAO;QAGPkB,CAAA,GAAAnB,YAAiB,CAAAoB,MAAA,gBAAA1B,IAAA,CAAAQ,QAAA;UACXU,IAAA,EAAM;QAGR;MACF;MACDX,GAAA;QACDkB,CAAA,CAAAE,OAAO;MAEP;IACA;IACDb,YAAA,CAAAc,MAAA;IAED,KAAG,IAAAC,CAAA,MAAAA,CAAA,MAAAA,CAAA,IAAAN,KAAA,CAAAM,CAAA;IACD3B,MAAA,CAAAY,YAAa,EAAAgB,OAAS,EAEtB,OAAI,CACJ;IACEf,cAAI,KAAa,QAAOA,cAAA,KAAC,cAAS,IAAKA,cAAW;IACpD,MAACO,KAAA;IAEDf,GAAA,CAAI,YAAM;MACRD,YAAU,CAAAoB,MAAA,gBAAC1B,IAAA,CAASQ,QAAK;QAC1BU,IAAA;MAED;IAEA;IACAhB,MAAM,CAAAY,YAAM,EAAAgB,OAAA,EACb,SACF,Q","ignoreList":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "one",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.20.0",
|
|
4
4
|
"license": "BSD-3-Clause",
|
|
5
5
|
"sideEffects": [
|
|
6
6
|
"setup.mjs",
|
|
@@ -186,18 +186,18 @@
|
|
|
186
186
|
"@cloudflare/vite-plugin": "^1.33.1",
|
|
187
187
|
"@swc/core": "^1.14.0",
|
|
188
188
|
"@ungap/structured-clone": "^1.2.0",
|
|
189
|
-
"@vxrn/color-scheme": "1.
|
|
190
|
-
"@vxrn/compiler": "1.
|
|
191
|
-
"@vxrn/resolve": "1.
|
|
192
|
-
"@vxrn/tslib-lite": "1.
|
|
193
|
-
"@vxrn/use-isomorphic-layout-effect": "1.
|
|
194
|
-
"@vxrn/vite-plugin-metro": "1.
|
|
189
|
+
"@vxrn/color-scheme": "1.20.0",
|
|
190
|
+
"@vxrn/compiler": "1.20.0",
|
|
191
|
+
"@vxrn/resolve": "1.20.0",
|
|
192
|
+
"@vxrn/tslib-lite": "1.20.0",
|
|
193
|
+
"@vxrn/use-isomorphic-layout-effect": "1.20.0",
|
|
194
|
+
"@vxrn/vite-plugin-metro": "1.20.0",
|
|
195
195
|
"babel-dead-code-elimination": "1.0.10",
|
|
196
196
|
"babel-plugin-module-resolver": "^5.0.2",
|
|
197
197
|
"babel-preset-expo": "~55.0.0",
|
|
198
198
|
"citty": "^0.1.6",
|
|
199
199
|
"core-js": "^3.38.1",
|
|
200
|
-
"create-vxrn": "1.
|
|
200
|
+
"create-vxrn": "1.20.0",
|
|
201
201
|
"escape-string-regexp": "^5.0.0",
|
|
202
202
|
"expo-linking": "~55.0.7",
|
|
203
203
|
"expo-modules-core": "~55.0.16",
|
|
@@ -217,7 +217,7 @@
|
|
|
217
217
|
"tsconfig-paths": "^4",
|
|
218
218
|
"use-latest-callback": "^0.2.3",
|
|
219
219
|
"vite": "^8.0.13",
|
|
220
|
-
"vxrn": "1.
|
|
220
|
+
"vxrn": "1.20.0",
|
|
221
221
|
"ws": "^8.21.0",
|
|
222
222
|
"xxhashjs": "^0.2.2"
|
|
223
223
|
},
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest'
|
|
2
|
+
import React from 'react'
|
|
3
|
+
import TestRenderer, { act } from 'react-test-renderer'
|
|
4
|
+
|
|
5
|
+
// run the focus effect like a plain mounted effect (i.e. screen is focused)
|
|
6
|
+
vi.mock('../useFocusEffect', () => ({
|
|
7
|
+
useFocusEffect: (effect: () => void | (() => void), deps: any[]) =>
|
|
8
|
+
React.useEffect(effect, deps),
|
|
9
|
+
}))
|
|
10
|
+
|
|
11
|
+
const replaceCalls: string[] = []
|
|
12
|
+
let resolvePending: (() => void) | undefined
|
|
13
|
+
|
|
14
|
+
vi.mock('../hooks', () => ({
|
|
15
|
+
useRouter: () => ({
|
|
16
|
+
replace: (href: string) => {
|
|
17
|
+
replaceCalls.push(href)
|
|
18
|
+
// a pending promise mimics linkTo awaiting route preload before dispatch
|
|
19
|
+
return new Promise<void>((resolve) => {
|
|
20
|
+
resolvePending = resolve
|
|
21
|
+
})
|
|
22
|
+
},
|
|
23
|
+
}),
|
|
24
|
+
}))
|
|
25
|
+
|
|
26
|
+
import { Redirect } from './Redirect'
|
|
27
|
+
|
|
28
|
+
async function flush() {
|
|
29
|
+
// let the replace() promise's .finally run
|
|
30
|
+
await act(async () => {
|
|
31
|
+
await Promise.resolve()
|
|
32
|
+
await Promise.resolve()
|
|
33
|
+
})
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
describe('Redirect', () => {
|
|
37
|
+
it('fires replace once across the mount/unmount churn of an auth-state settle', async () => {
|
|
38
|
+
replaceCalls.length = 0
|
|
39
|
+
|
|
40
|
+
// simulate the auth gate flipping logged-in -> loading -> logged-in -> ...
|
|
41
|
+
// each flip remounts <Redirect> while the first replace is still in flight
|
|
42
|
+
for (let i = 0; i < 8; i++) {
|
|
43
|
+
let r: TestRenderer.ReactTestRenderer
|
|
44
|
+
act(() => {
|
|
45
|
+
r = TestRenderer.create(<Redirect href="/home" />)
|
|
46
|
+
})
|
|
47
|
+
act(() => {
|
|
48
|
+
r!.unmount()
|
|
49
|
+
})
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
expect(replaceCalls).toEqual(['/home'])
|
|
53
|
+
|
|
54
|
+
// once the navigation settles, the in-flight guard clears...
|
|
55
|
+
resolvePending?.()
|
|
56
|
+
await flush()
|
|
57
|
+
|
|
58
|
+
// ...so a genuinely new redirect to the same target fires again
|
|
59
|
+
act(() => {
|
|
60
|
+
TestRenderer.create(<Redirect href="/home" />)
|
|
61
|
+
})
|
|
62
|
+
expect(replaceCalls).toEqual(['/home', '/home'])
|
|
63
|
+
|
|
64
|
+
resolvePending?.()
|
|
65
|
+
await flush()
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
it('does not re-fire within a single mounted instance', async () => {
|
|
69
|
+
replaceCalls.length = 0
|
|
70
|
+
|
|
71
|
+
let r: TestRenderer.ReactTestRenderer
|
|
72
|
+
act(() => {
|
|
73
|
+
r = TestRenderer.create(<Redirect href="/login" />)
|
|
74
|
+
})
|
|
75
|
+
// a re-render of the same instance must not re-issue navigation
|
|
76
|
+
act(() => {
|
|
77
|
+
r!.update(<Redirect href="/login" />)
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
expect(replaceCalls).toEqual(['/login'])
|
|
81
|
+
|
|
82
|
+
resolvePending?.()
|
|
83
|
+
await flush()
|
|
84
|
+
})
|
|
85
|
+
})
|
package/src/link/Redirect.tsx
CHANGED
|
@@ -3,6 +3,19 @@ import { useRouter } from '../hooks'
|
|
|
3
3
|
import type { OneRouter } from '../interfaces/router'
|
|
4
4
|
import { useFocusEffect } from '../useFocusEffect'
|
|
5
5
|
|
|
6
|
+
// module-scoped guard shared across every <Redirect> instance, keyed by href.
|
|
7
|
+
//
|
|
8
|
+
// an auth-gate layout conditionally renders <Redirect> (logged-in) vs
|
|
9
|
+
// <Slot>/null (loading), so while auth state settles it mounts and unmounts
|
|
10
|
+
// Redirect repeatedly. each remount is a fresh instance with a fresh
|
|
11
|
+
// `hasRedirected` ref, and `router.replace` is async (it awaits route preload
|
|
12
|
+
// before it ever dispatches), so a per-instance ref can't stop the next remount
|
|
13
|
+
// from firing another concurrent replace into the same target during that await
|
|
14
|
+
// window. the pile of resetRoot dispatches then throws "Maximum update depth
|
|
15
|
+
// exceeded". this guard dedupes across instances; it's cleared once the
|
|
16
|
+
// navigation promise settles, so a later redirect to the same href still fires.
|
|
17
|
+
const redirectsInFlight = new Set<string>()
|
|
18
|
+
|
|
6
19
|
/** Redirects to the href as soon as the component is mounted. */
|
|
7
20
|
|
|
8
21
|
export function Redirect({ href }: { href: OneRouter.Href }) {
|
|
@@ -11,12 +24,23 @@ export function Redirect({ href }: { href: OneRouter.Href }) {
|
|
|
11
24
|
|
|
12
25
|
useFocusEffect(() => {
|
|
13
26
|
if (hasRedirected.current) return
|
|
27
|
+
|
|
28
|
+
const key = typeof href === 'string' ? href : JSON.stringify(href)
|
|
29
|
+
// a redirect to this href is already in flight from another (re)mount
|
|
30
|
+
if (redirectsInFlight.has(key)) return
|
|
31
|
+
|
|
14
32
|
hasRedirected.current = true
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
33
|
+
redirectsInFlight.add(key)
|
|
34
|
+
|
|
35
|
+
Promise.resolve(router.replace(href))
|
|
36
|
+
.catch((error) => {
|
|
37
|
+
// navigation failed, let a future mount retry
|
|
38
|
+
hasRedirected.current = false
|
|
39
|
+
console.error(error)
|
|
40
|
+
})
|
|
41
|
+
.finally(() => {
|
|
42
|
+
redirectsInFlight.delete(key)
|
|
43
|
+
})
|
|
20
44
|
}, [href])
|
|
21
45
|
|
|
22
46
|
return null
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Redirect.d.ts","sourceRoot":"","sources":["../../src/link/Redirect.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAA;
|
|
1
|
+
{"version":3,"file":"Redirect.d.ts","sourceRoot":"","sources":["../../src/link/Redirect.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAA;AAgBrD,iEAAiE;AAEjE,wBAAgB,QAAQ,CAAC,EAAE,IAAI,EAAE,EAAE;IAAE,IAAI,EAAE,SAAS,CAAC,IAAI,CAAA;CAAE,QA0B1D"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Redirect.test.d.ts","sourceRoot":"","sources":["../../src/link/Redirect.test.tsx"],"names":[],"mappings":""}
|