@remix-run/router 1.6.3 → 1.7.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/CHANGELOG.md +53 -0
- package/dist/history.d.ts +6 -6
- package/dist/router.cjs.js +378 -234
- package/dist/router.cjs.js.map +1 -1
- package/dist/router.d.ts +69 -41
- package/dist/router.js +365 -229
- package/dist/router.js.map +1 -1
- package/dist/router.umd.js +378 -234
- package/dist/router.umd.js.map +1 -1
- package/dist/router.umd.min.js +2 -2
- package/dist/router.umd.min.js.map +1 -1
- package/dist/utils.d.ts +54 -28
- package/package.json +1 -1
- package/router.ts +501 -277
- package/utils.ts +44 -20
package/router.ts
CHANGED
|
@@ -147,7 +147,7 @@ export interface Router {
|
|
|
147
147
|
key: string,
|
|
148
148
|
routeId: string,
|
|
149
149
|
href: string | null,
|
|
150
|
-
opts?:
|
|
150
|
+
opts?: RouterFetchOptions
|
|
151
151
|
): void;
|
|
152
152
|
|
|
153
153
|
/**
|
|
@@ -419,41 +419,59 @@ export interface GetScrollPositionFunction {
|
|
|
419
419
|
|
|
420
420
|
export type RelativeRoutingType = "route" | "path";
|
|
421
421
|
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
state?: any;
|
|
422
|
+
// Allowed for any navigation or fetch
|
|
423
|
+
type BaseNavigateOrFetchOptions = {
|
|
425
424
|
preventScrollReset?: boolean;
|
|
426
425
|
relative?: RelativeRoutingType;
|
|
426
|
+
};
|
|
427
|
+
|
|
428
|
+
// Only allowed for navigations
|
|
429
|
+
type BaseNavigateOptions = BaseNavigateOrFetchOptions & {
|
|
430
|
+
replace?: boolean;
|
|
431
|
+
state?: any;
|
|
427
432
|
fromRouteId?: string;
|
|
428
433
|
};
|
|
429
434
|
|
|
435
|
+
// Only allowed for submission navigations
|
|
436
|
+
type BaseSubmissionOptions = {
|
|
437
|
+
formMethod?: HTMLFormMethod;
|
|
438
|
+
formEncType?: FormEncType;
|
|
439
|
+
} & (
|
|
440
|
+
| { formData: FormData; body?: undefined }
|
|
441
|
+
| { formData?: undefined; body: any }
|
|
442
|
+
);
|
|
443
|
+
|
|
430
444
|
/**
|
|
431
|
-
* Options for a navigate() call for a
|
|
445
|
+
* Options for a navigate() call for a normal (non-submission) navigation
|
|
432
446
|
*/
|
|
433
447
|
type LinkNavigateOptions = BaseNavigateOptions;
|
|
434
448
|
|
|
435
449
|
/**
|
|
436
|
-
* Options for a navigate() call for a
|
|
450
|
+
* Options for a navigate() call for a submission navigation
|
|
437
451
|
*/
|
|
438
|
-
type SubmissionNavigateOptions = BaseNavigateOptions &
|
|
439
|
-
formMethod?: HTMLFormMethod;
|
|
440
|
-
formEncType?: FormEncType;
|
|
441
|
-
formData: FormData;
|
|
442
|
-
};
|
|
452
|
+
type SubmissionNavigateOptions = BaseNavigateOptions & BaseSubmissionOptions;
|
|
443
453
|
|
|
444
454
|
/**
|
|
445
|
-
* Options to pass to navigate() for
|
|
455
|
+
* Options to pass to navigate() for a navigation
|
|
446
456
|
*/
|
|
447
457
|
export type RouterNavigateOptions =
|
|
448
458
|
| LinkNavigateOptions
|
|
449
459
|
| SubmissionNavigateOptions;
|
|
450
460
|
|
|
461
|
+
/**
|
|
462
|
+
* Options for a fetch() load
|
|
463
|
+
*/
|
|
464
|
+
type LoadFetchOptions = BaseNavigateOrFetchOptions;
|
|
465
|
+
|
|
466
|
+
/**
|
|
467
|
+
* Options for a fetch() submission
|
|
468
|
+
*/
|
|
469
|
+
type SubmitFetchOptions = BaseNavigateOrFetchOptions & BaseSubmissionOptions;
|
|
470
|
+
|
|
451
471
|
/**
|
|
452
472
|
* Options to pass to fetch()
|
|
453
473
|
*/
|
|
454
|
-
export type RouterFetchOptions =
|
|
455
|
-
| Omit<LinkNavigateOptions, "replace">
|
|
456
|
-
| Omit<SubmissionNavigateOptions, "replace">;
|
|
474
|
+
export type RouterFetchOptions = LoadFetchOptions | SubmitFetchOptions;
|
|
457
475
|
|
|
458
476
|
/**
|
|
459
477
|
* Potential states for state.navigation
|
|
@@ -466,22 +484,28 @@ export type NavigationStates = {
|
|
|
466
484
|
formAction: undefined;
|
|
467
485
|
formEncType: undefined;
|
|
468
486
|
formData: undefined;
|
|
487
|
+
json: undefined;
|
|
488
|
+
text: undefined;
|
|
469
489
|
};
|
|
470
490
|
Loading: {
|
|
471
491
|
state: "loading";
|
|
472
492
|
location: Location;
|
|
473
|
-
formMethod:
|
|
474
|
-
formAction:
|
|
475
|
-
formEncType:
|
|
476
|
-
formData:
|
|
493
|
+
formMethod: Submission["formMethod"] | undefined;
|
|
494
|
+
formAction: Submission["formAction"] | undefined;
|
|
495
|
+
formEncType: Submission["formEncType"] | undefined;
|
|
496
|
+
formData: Submission["formData"] | undefined;
|
|
497
|
+
json: Submission["json"] | undefined;
|
|
498
|
+
text: Submission["text"] | undefined;
|
|
477
499
|
};
|
|
478
500
|
Submitting: {
|
|
479
501
|
state: "submitting";
|
|
480
502
|
location: Location;
|
|
481
|
-
formMethod:
|
|
482
|
-
formAction:
|
|
483
|
-
formEncType:
|
|
484
|
-
formData:
|
|
503
|
+
formMethod: Submission["formMethod"];
|
|
504
|
+
formAction: Submission["formAction"];
|
|
505
|
+
formEncType: Submission["formEncType"];
|
|
506
|
+
formData: Submission["formData"];
|
|
507
|
+
json: Submission["json"];
|
|
508
|
+
text: Submission["text"];
|
|
485
509
|
};
|
|
486
510
|
};
|
|
487
511
|
|
|
@@ -498,25 +522,31 @@ type FetcherStates<TData = any> = {
|
|
|
498
522
|
formMethod: undefined;
|
|
499
523
|
formAction: undefined;
|
|
500
524
|
formEncType: undefined;
|
|
525
|
+
text: undefined;
|
|
501
526
|
formData: undefined;
|
|
527
|
+
json: undefined;
|
|
502
528
|
data: TData | undefined;
|
|
503
529
|
" _hasFetcherDoneAnything "?: boolean;
|
|
504
530
|
};
|
|
505
531
|
Loading: {
|
|
506
532
|
state: "loading";
|
|
507
|
-
formMethod:
|
|
508
|
-
formAction:
|
|
509
|
-
formEncType:
|
|
510
|
-
|
|
533
|
+
formMethod: Submission["formMethod"] | undefined;
|
|
534
|
+
formAction: Submission["formAction"] | undefined;
|
|
535
|
+
formEncType: Submission["formEncType"] | undefined;
|
|
536
|
+
text: Submission["text"] | undefined;
|
|
537
|
+
formData: Submission["formData"] | undefined;
|
|
538
|
+
json: Submission["json"] | undefined;
|
|
511
539
|
data: TData | undefined;
|
|
512
540
|
" _hasFetcherDoneAnything "?: boolean;
|
|
513
541
|
};
|
|
514
542
|
Submitting: {
|
|
515
543
|
state: "submitting";
|
|
516
|
-
formMethod:
|
|
517
|
-
formAction:
|
|
518
|
-
formEncType:
|
|
519
|
-
|
|
544
|
+
formMethod: Submission["formMethod"];
|
|
545
|
+
formAction: Submission["formAction"];
|
|
546
|
+
formEncType: Submission["formEncType"];
|
|
547
|
+
text: Submission["text"];
|
|
548
|
+
formData: Submission["formData"];
|
|
549
|
+
json: Submission["json"];
|
|
520
550
|
data: TData | undefined;
|
|
521
551
|
" _hasFetcherDoneAnything "?: boolean;
|
|
522
552
|
};
|
|
@@ -642,6 +672,8 @@ export const IDLE_NAVIGATION: NavigationStates["Idle"] = {
|
|
|
642
672
|
formAction: undefined,
|
|
643
673
|
formEncType: undefined,
|
|
644
674
|
formData: undefined,
|
|
675
|
+
json: undefined,
|
|
676
|
+
text: undefined,
|
|
645
677
|
};
|
|
646
678
|
|
|
647
679
|
export const IDLE_FETCHER: FetcherStates["Idle"] = {
|
|
@@ -651,6 +683,8 @@ export const IDLE_FETCHER: FetcherStates["Idle"] = {
|
|
|
651
683
|
formAction: undefined,
|
|
652
684
|
formEncType: undefined,
|
|
653
685
|
formData: undefined,
|
|
686
|
+
json: undefined,
|
|
687
|
+
text: undefined,
|
|
654
688
|
};
|
|
655
689
|
|
|
656
690
|
export const IDLE_BLOCKER: BlockerUnblocked = {
|
|
@@ -893,8 +927,9 @@ export function createRouter(init: RouterInit): Router {
|
|
|
893
927
|
init.history.go(delta);
|
|
894
928
|
},
|
|
895
929
|
reset() {
|
|
896
|
-
|
|
897
|
-
|
|
930
|
+
let blockers = new Map(state.blockers);
|
|
931
|
+
blockers.set(blockerKey!, IDLE_BLOCKER);
|
|
932
|
+
updateState({ blockers });
|
|
898
933
|
},
|
|
899
934
|
});
|
|
900
935
|
return;
|
|
@@ -991,9 +1026,8 @@ export function createRouter(init: RouterInit): Router {
|
|
|
991
1026
|
|
|
992
1027
|
// On a successful navigation we can assume we got through all blockers
|
|
993
1028
|
// so we can start fresh
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
}
|
|
1029
|
+
let blockers = new Map();
|
|
1030
|
+
blockerFunctions.clear();
|
|
997
1031
|
|
|
998
1032
|
// Always respect the user flag. Otherwise don't reset on mutation
|
|
999
1033
|
// submission navigations unless they redirect
|
|
@@ -1008,6 +1042,16 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1008
1042
|
inFlightDataRoutes = undefined;
|
|
1009
1043
|
}
|
|
1010
1044
|
|
|
1045
|
+
if (isUninterruptedRevalidation) {
|
|
1046
|
+
// If this was an uninterrupted revalidation then do not touch history
|
|
1047
|
+
} else if (pendingAction === HistoryAction.Pop) {
|
|
1048
|
+
// Do nothing for POP - URL has already been updated
|
|
1049
|
+
} else if (pendingAction === HistoryAction.Push) {
|
|
1050
|
+
init.history.push(location, location.state);
|
|
1051
|
+
} else if (pendingAction === HistoryAction.Replace) {
|
|
1052
|
+
init.history.replace(location, location.state);
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1011
1055
|
updateState({
|
|
1012
1056
|
...newState, // matches, errors, fetchers go through as-is
|
|
1013
1057
|
actionData,
|
|
@@ -1022,19 +1066,9 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1022
1066
|
newState.matches || state.matches
|
|
1023
1067
|
),
|
|
1024
1068
|
preventScrollReset,
|
|
1025
|
-
blockers
|
|
1069
|
+
blockers,
|
|
1026
1070
|
});
|
|
1027
1071
|
|
|
1028
|
-
if (isUninterruptedRevalidation) {
|
|
1029
|
-
// If this was an uninterrupted revalidation then do not touch history
|
|
1030
|
-
} else if (pendingAction === HistoryAction.Pop) {
|
|
1031
|
-
// Do nothing for POP - URL has already been updated
|
|
1032
|
-
} else if (pendingAction === HistoryAction.Push) {
|
|
1033
|
-
init.history.push(location, location.state);
|
|
1034
|
-
} else if (pendingAction === HistoryAction.Replace) {
|
|
1035
|
-
init.history.replace(location, location.state);
|
|
1036
|
-
}
|
|
1037
|
-
|
|
1038
1072
|
// Reset stateful navigation vars
|
|
1039
1073
|
pendingAction = HistoryAction.Pop;
|
|
1040
1074
|
pendingPreventScrollReset = false;
|
|
@@ -1114,6 +1148,7 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1114
1148
|
nextLocation,
|
|
1115
1149
|
historyAction,
|
|
1116
1150
|
});
|
|
1151
|
+
|
|
1117
1152
|
if (blockerKey) {
|
|
1118
1153
|
// Put the blocker into a blocked state
|
|
1119
1154
|
updateBlocker(blockerKey, {
|
|
@@ -1130,8 +1165,9 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1130
1165
|
navigate(to, opts);
|
|
1131
1166
|
},
|
|
1132
1167
|
reset() {
|
|
1133
|
-
|
|
1134
|
-
|
|
1168
|
+
let blockers = new Map(state.blockers);
|
|
1169
|
+
blockers.set(blockerKey!, IDLE_BLOCKER);
|
|
1170
|
+
updateState({ blockers });
|
|
1135
1171
|
},
|
|
1136
1172
|
});
|
|
1137
1173
|
return;
|
|
@@ -1286,13 +1322,7 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1286
1322
|
|
|
1287
1323
|
pendingActionData = actionOutput.pendingActionData;
|
|
1288
1324
|
pendingError = actionOutput.pendingActionError;
|
|
1289
|
-
|
|
1290
|
-
let navigation: NavigationStates["Loading"] = {
|
|
1291
|
-
state: "loading",
|
|
1292
|
-
location,
|
|
1293
|
-
...opts.submission,
|
|
1294
|
-
};
|
|
1295
|
-
loadingNavigation = navigation;
|
|
1325
|
+
loadingNavigation = getLoadingNavigation(location, opts.submission);
|
|
1296
1326
|
|
|
1297
1327
|
// Create a GET request for the loaders
|
|
1298
1328
|
request = new Request(request.url, { signal: request.signal });
|
|
@@ -1335,16 +1365,12 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1335
1365
|
location: Location,
|
|
1336
1366
|
submission: Submission,
|
|
1337
1367
|
matches: AgnosticDataRouteMatch[],
|
|
1338
|
-
opts
|
|
1368
|
+
opts: { replace?: boolean } = {}
|
|
1339
1369
|
): Promise<HandleActionResult> {
|
|
1340
1370
|
interruptActiveLoads();
|
|
1341
1371
|
|
|
1342
1372
|
// Put us in a submitting state
|
|
1343
|
-
let navigation
|
|
1344
|
-
state: "submitting",
|
|
1345
|
-
location,
|
|
1346
|
-
...submission,
|
|
1347
|
-
};
|
|
1373
|
+
let navigation = getSubmittingNavigation(location, submission);
|
|
1348
1374
|
updateState({ navigation });
|
|
1349
1375
|
|
|
1350
1376
|
// Call our action and get the result
|
|
@@ -1434,36 +1460,15 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1434
1460
|
pendingError?: RouteData
|
|
1435
1461
|
): Promise<HandleLoadersResult> {
|
|
1436
1462
|
// Figure out the right navigation we want to use for data loading
|
|
1437
|
-
let loadingNavigation =
|
|
1438
|
-
|
|
1439
|
-
let navigation: NavigationStates["Loading"] = {
|
|
1440
|
-
state: "loading",
|
|
1441
|
-
location,
|
|
1442
|
-
formMethod: undefined,
|
|
1443
|
-
formAction: undefined,
|
|
1444
|
-
formEncType: undefined,
|
|
1445
|
-
formData: undefined,
|
|
1446
|
-
...submission,
|
|
1447
|
-
};
|
|
1448
|
-
loadingNavigation = navigation;
|
|
1449
|
-
}
|
|
1463
|
+
let loadingNavigation =
|
|
1464
|
+
overrideNavigation || getLoadingNavigation(location, submission);
|
|
1450
1465
|
|
|
1451
1466
|
// If this was a redirect from an action we don't have a "submission" but
|
|
1452
1467
|
// we have it on the loading navigation so use that if available
|
|
1453
1468
|
let activeSubmission =
|
|
1454
|
-
submission ||
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
loadingNavigation.formAction &&
|
|
1458
|
-
loadingNavigation.formData &&
|
|
1459
|
-
loadingNavigation.formEncType
|
|
1460
|
-
? {
|
|
1461
|
-
formMethod: loadingNavigation.formMethod,
|
|
1462
|
-
formAction: loadingNavigation.formAction,
|
|
1463
|
-
formData: loadingNavigation.formData,
|
|
1464
|
-
formEncType: loadingNavigation.formEncType,
|
|
1465
|
-
}
|
|
1466
|
-
: undefined;
|
|
1469
|
+
submission ||
|
|
1470
|
+
fetcherSubmission ||
|
|
1471
|
+
getSubmissionFromNavigation(loadingNavigation);
|
|
1467
1472
|
|
|
1468
1473
|
let routesToUse = inFlightDataRoutes || dataRoutes;
|
|
1469
1474
|
let [matchesToLoad, revalidatingFetchers] = getMatchesToLoad(
|
|
@@ -1476,6 +1481,7 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1476
1481
|
cancelledDeferredRoutes,
|
|
1477
1482
|
cancelledFetcherLoads,
|
|
1478
1483
|
fetchLoadMatches,
|
|
1484
|
+
fetchRedirectIds,
|
|
1479
1485
|
routesToUse,
|
|
1480
1486
|
basename,
|
|
1481
1487
|
pendingActionData,
|
|
@@ -1512,15 +1518,10 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1512
1518
|
if (!isUninterruptedRevalidation) {
|
|
1513
1519
|
revalidatingFetchers.forEach((rf) => {
|
|
1514
1520
|
let fetcher = state.fetchers.get(rf.key);
|
|
1515
|
-
let revalidatingFetcher
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
formAction: undefined,
|
|
1520
|
-
formEncType: undefined,
|
|
1521
|
-
formData: undefined,
|
|
1522
|
-
" _hasFetcherDoneAnything ": true,
|
|
1523
|
-
};
|
|
1521
|
+
let revalidatingFetcher = getLoadingFetcher(
|
|
1522
|
+
undefined,
|
|
1523
|
+
fetcher ? fetcher.data : undefined
|
|
1524
|
+
);
|
|
1524
1525
|
state.fetchers.set(rf.key, revalidatingFetcher);
|
|
1525
1526
|
});
|
|
1526
1527
|
let actionData = pendingActionData || state.actionData;
|
|
@@ -1539,6 +1540,9 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1539
1540
|
|
|
1540
1541
|
pendingNavigationLoadId = ++incrementingLoadId;
|
|
1541
1542
|
revalidatingFetchers.forEach((rf) => {
|
|
1543
|
+
if (fetchControllers.has(rf.key)) {
|
|
1544
|
+
abortFetcher(rf.key);
|
|
1545
|
+
}
|
|
1542
1546
|
if (rf.controller) {
|
|
1543
1547
|
// Fetchers use an independent AbortController so that aborting a fetcher
|
|
1544
1548
|
// (via deleteFetcher) does not abort the triggering navigation that
|
|
@@ -1666,12 +1670,18 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1666
1670
|
return;
|
|
1667
1671
|
}
|
|
1668
1672
|
|
|
1669
|
-
let { path, submission } = normalizeNavigateOptions(
|
|
1673
|
+
let { path, submission, error } = normalizeNavigateOptions(
|
|
1670
1674
|
future.v7_normalizeFormMethod,
|
|
1671
1675
|
true,
|
|
1672
1676
|
normalizedPath,
|
|
1673
1677
|
opts
|
|
1674
1678
|
);
|
|
1679
|
+
|
|
1680
|
+
if (error) {
|
|
1681
|
+
setFetcherError(key, routeId, error);
|
|
1682
|
+
return;
|
|
1683
|
+
}
|
|
1684
|
+
|
|
1675
1685
|
let match = getTargetMatch(matches, path);
|
|
1676
1686
|
|
|
1677
1687
|
pendingPreventScrollReset = (opts && opts.preventScrollReset) === true;
|
|
@@ -1712,12 +1722,7 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1712
1722
|
|
|
1713
1723
|
// Put this fetcher into it's submitting state
|
|
1714
1724
|
let existingFetcher = state.fetchers.get(key);
|
|
1715
|
-
let fetcher
|
|
1716
|
-
state: "submitting",
|
|
1717
|
-
...submission,
|
|
1718
|
-
data: existingFetcher && existingFetcher.data,
|
|
1719
|
-
" _hasFetcherDoneAnything ": true,
|
|
1720
|
-
};
|
|
1725
|
+
let fetcher = getSubmittingFetcher(submission, existingFetcher);
|
|
1721
1726
|
state.fetchers.set(key, fetcher);
|
|
1722
1727
|
updateState({ fetchers: new Map(state.fetchers) });
|
|
1723
1728
|
|
|
@@ -1753,12 +1758,7 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1753
1758
|
if (isRedirectResult(actionResult)) {
|
|
1754
1759
|
fetchControllers.delete(key);
|
|
1755
1760
|
fetchRedirectIds.add(key);
|
|
1756
|
-
let loadingFetcher
|
|
1757
|
-
state: "loading",
|
|
1758
|
-
...submission,
|
|
1759
|
-
data: undefined,
|
|
1760
|
-
" _hasFetcherDoneAnything ": true,
|
|
1761
|
-
};
|
|
1761
|
+
let loadingFetcher = getLoadingFetcher(submission);
|
|
1762
1762
|
state.fetchers.set(key, loadingFetcher);
|
|
1763
1763
|
updateState({ fetchers: new Map(state.fetchers) });
|
|
1764
1764
|
|
|
@@ -1797,12 +1797,7 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1797
1797
|
let loadId = ++incrementingLoadId;
|
|
1798
1798
|
fetchReloadIds.set(key, loadId);
|
|
1799
1799
|
|
|
1800
|
-
let loadFetcher
|
|
1801
|
-
state: "loading",
|
|
1802
|
-
data: actionResult.data,
|
|
1803
|
-
...submission,
|
|
1804
|
-
" _hasFetcherDoneAnything ": true,
|
|
1805
|
-
};
|
|
1800
|
+
let loadFetcher = getLoadingFetcher(submission, actionResult.data);
|
|
1806
1801
|
state.fetchers.set(key, loadFetcher);
|
|
1807
1802
|
|
|
1808
1803
|
let [matchesToLoad, revalidatingFetchers] = getMatchesToLoad(
|
|
@@ -1815,6 +1810,7 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1815
1810
|
cancelledDeferredRoutes,
|
|
1816
1811
|
cancelledFetcherLoads,
|
|
1817
1812
|
fetchLoadMatches,
|
|
1813
|
+
fetchRedirectIds,
|
|
1818
1814
|
routesToUse,
|
|
1819
1815
|
basename,
|
|
1820
1816
|
{ [match.route.id]: actionResult.data },
|
|
@@ -1829,16 +1825,14 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1829
1825
|
.forEach((rf) => {
|
|
1830
1826
|
let staleKey = rf.key;
|
|
1831
1827
|
let existingFetcher = state.fetchers.get(staleKey);
|
|
1832
|
-
let revalidatingFetcher
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
formAction: undefined,
|
|
1837
|
-
formEncType: undefined,
|
|
1838
|
-
formData: undefined,
|
|
1839
|
-
" _hasFetcherDoneAnything ": true,
|
|
1840
|
-
};
|
|
1828
|
+
let revalidatingFetcher = getLoadingFetcher(
|
|
1829
|
+
undefined,
|
|
1830
|
+
existingFetcher ? existingFetcher.data : undefined
|
|
1831
|
+
);
|
|
1841
1832
|
state.fetchers.set(staleKey, revalidatingFetcher);
|
|
1833
|
+
if (fetchControllers.has(staleKey)) {
|
|
1834
|
+
abortFetcher(staleKey);
|
|
1835
|
+
}
|
|
1842
1836
|
if (rf.controller) {
|
|
1843
1837
|
fetchControllers.set(staleKey, rf.controller);
|
|
1844
1838
|
}
|
|
@@ -1896,15 +1890,7 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1896
1890
|
// Since we let revalidations complete even if the submitting fetcher was
|
|
1897
1891
|
// deleted, only put it back to idle if it hasn't been deleted
|
|
1898
1892
|
if (state.fetchers.has(key)) {
|
|
1899
|
-
let doneFetcher
|
|
1900
|
-
state: "idle",
|
|
1901
|
-
data: actionResult.data,
|
|
1902
|
-
formMethod: undefined,
|
|
1903
|
-
formAction: undefined,
|
|
1904
|
-
formEncType: undefined,
|
|
1905
|
-
formData: undefined,
|
|
1906
|
-
" _hasFetcherDoneAnything ": true,
|
|
1907
|
-
};
|
|
1893
|
+
let doneFetcher = getDoneFetcher(actionResult.data);
|
|
1908
1894
|
state.fetchers.set(key, doneFetcher);
|
|
1909
1895
|
}
|
|
1910
1896
|
|
|
@@ -1957,16 +1943,10 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1957
1943
|
) {
|
|
1958
1944
|
let existingFetcher = state.fetchers.get(key);
|
|
1959
1945
|
// Put this fetcher into it's loading state
|
|
1960
|
-
let loadingFetcher
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
formEncType: undefined,
|
|
1965
|
-
formData: undefined,
|
|
1966
|
-
...submission,
|
|
1967
|
-
data: existingFetcher && existingFetcher.data,
|
|
1968
|
-
" _hasFetcherDoneAnything ": true,
|
|
1969
|
-
};
|
|
1946
|
+
let loadingFetcher = getLoadingFetcher(
|
|
1947
|
+
submission,
|
|
1948
|
+
existingFetcher ? existingFetcher.data : undefined
|
|
1949
|
+
);
|
|
1970
1950
|
state.fetchers.set(key, loadingFetcher);
|
|
1971
1951
|
updateState({ fetchers: new Map(state.fetchers) });
|
|
1972
1952
|
|
|
@@ -2035,15 +2015,7 @@ export function createRouter(init: RouterInit): Router {
|
|
|
2035
2015
|
invariant(!isDeferredResult(result), "Unhandled fetcher deferred data");
|
|
2036
2016
|
|
|
2037
2017
|
// Put the fetcher back into an idle state
|
|
2038
|
-
let doneFetcher
|
|
2039
|
-
state: "idle",
|
|
2040
|
-
data: result.data,
|
|
2041
|
-
formMethod: undefined,
|
|
2042
|
-
formAction: undefined,
|
|
2043
|
-
formEncType: undefined,
|
|
2044
|
-
formData: undefined,
|
|
2045
|
-
" _hasFetcherDoneAnything ": true,
|
|
2046
|
-
};
|
|
2018
|
+
let doneFetcher = getDoneFetcher(result.data);
|
|
2047
2019
|
state.fetchers.set(key, doneFetcher);
|
|
2048
2020
|
updateState({ fetchers: new Map(state.fetchers) });
|
|
2049
2021
|
}
|
|
@@ -2121,27 +2093,20 @@ export function createRouter(init: RouterInit): Router {
|
|
|
2121
2093
|
|
|
2122
2094
|
// Use the incoming submission if provided, fallback on the active one in
|
|
2123
2095
|
// state.navigation
|
|
2124
|
-
let
|
|
2125
|
-
|
|
2126
|
-
submission = {
|
|
2127
|
-
formMethod,
|
|
2128
|
-
formAction,
|
|
2129
|
-
formEncType,
|
|
2130
|
-
formData,
|
|
2131
|
-
};
|
|
2132
|
-
}
|
|
2096
|
+
let activeSubmission =
|
|
2097
|
+
submission || getSubmissionFromNavigation(state.navigation);
|
|
2133
2098
|
|
|
2134
2099
|
// If this was a 307/308 submission we want to preserve the HTTP method and
|
|
2135
2100
|
// re-submit the GET/POST/PUT/PATCH/DELETE as a submission navigation to the
|
|
2136
2101
|
// redirected location
|
|
2137
2102
|
if (
|
|
2138
2103
|
redirectPreserveMethodStatusCodes.has(redirect.status) &&
|
|
2139
|
-
|
|
2140
|
-
isMutationMethod(
|
|
2104
|
+
activeSubmission &&
|
|
2105
|
+
isMutationMethod(activeSubmission.formMethod)
|
|
2141
2106
|
) {
|
|
2142
2107
|
await startNavigation(redirectHistoryAction, redirectLocation, {
|
|
2143
2108
|
submission: {
|
|
2144
|
-
...
|
|
2109
|
+
...activeSubmission,
|
|
2145
2110
|
formAction: redirect.location,
|
|
2146
2111
|
},
|
|
2147
2112
|
// Preserve this flag across redirects
|
|
@@ -2151,30 +2116,19 @@ export function createRouter(init: RouterInit): Router {
|
|
|
2151
2116
|
// For a fetch action redirect, we kick off a new loading navigation
|
|
2152
2117
|
// without the fetcher submission, but we send it along for shouldRevalidate
|
|
2153
2118
|
await startNavigation(redirectHistoryAction, redirectLocation, {
|
|
2154
|
-
overrideNavigation:
|
|
2155
|
-
|
|
2156
|
-
location: redirectLocation,
|
|
2157
|
-
formMethod: undefined,
|
|
2158
|
-
formAction: undefined,
|
|
2159
|
-
formEncType: undefined,
|
|
2160
|
-
formData: undefined,
|
|
2161
|
-
},
|
|
2162
|
-
fetcherSubmission: submission,
|
|
2119
|
+
overrideNavigation: getLoadingNavigation(redirectLocation),
|
|
2120
|
+
fetcherSubmission: activeSubmission,
|
|
2163
2121
|
// Preserve this flag across redirects
|
|
2164
2122
|
preventScrollReset: pendingPreventScrollReset,
|
|
2165
2123
|
});
|
|
2166
2124
|
} else {
|
|
2167
|
-
//
|
|
2168
|
-
|
|
2125
|
+
// If we have a submission, we will preserve it through the redirect navigation
|
|
2126
|
+
let overrideNavigation = getLoadingNavigation(
|
|
2127
|
+
redirectLocation,
|
|
2128
|
+
activeSubmission
|
|
2129
|
+
);
|
|
2169
2130
|
await startNavigation(redirectHistoryAction, redirectLocation, {
|
|
2170
|
-
overrideNavigation
|
|
2171
|
-
state: "loading",
|
|
2172
|
-
location: redirectLocation,
|
|
2173
|
-
formMethod: submission ? submission.formMethod : undefined,
|
|
2174
|
-
formAction: submission ? submission.formAction : undefined,
|
|
2175
|
-
formEncType: submission ? submission.formEncType : undefined,
|
|
2176
|
-
formData: submission ? submission.formData : undefined,
|
|
2177
|
-
},
|
|
2131
|
+
overrideNavigation,
|
|
2178
2132
|
// Preserve this flag across redirects
|
|
2179
2133
|
preventScrollReset: pendingPreventScrollReset,
|
|
2180
2134
|
});
|
|
@@ -2302,15 +2256,7 @@ export function createRouter(init: RouterInit): Router {
|
|
|
2302
2256
|
function markFetchersDone(keys: string[]) {
|
|
2303
2257
|
for (let key of keys) {
|
|
2304
2258
|
let fetcher = getFetcher(key);
|
|
2305
|
-
let doneFetcher
|
|
2306
|
-
state: "idle",
|
|
2307
|
-
data: fetcher.data,
|
|
2308
|
-
formMethod: undefined,
|
|
2309
|
-
formAction: undefined,
|
|
2310
|
-
formEncType: undefined,
|
|
2311
|
-
formData: undefined,
|
|
2312
|
-
" _hasFetcherDoneAnything ": true,
|
|
2313
|
-
};
|
|
2259
|
+
let doneFetcher = getDoneFetcher(fetcher.data);
|
|
2314
2260
|
state.fetchers.set(key, doneFetcher);
|
|
2315
2261
|
}
|
|
2316
2262
|
}
|
|
@@ -2378,8 +2324,9 @@ export function createRouter(init: RouterInit): Router {
|
|
|
2378
2324
|
`Invalid blocker state transition: ${blocker.state} -> ${newBlocker.state}`
|
|
2379
2325
|
);
|
|
2380
2326
|
|
|
2381
|
-
state.blockers
|
|
2382
|
-
|
|
2327
|
+
let blockers = new Map(state.blockers);
|
|
2328
|
+
blockers.set(key, newBlocker);
|
|
2329
|
+
updateState({ blockers });
|
|
2383
2330
|
}
|
|
2384
2331
|
|
|
2385
2332
|
function shouldBlockNavigation({
|
|
@@ -2444,7 +2391,7 @@ export function createRouter(init: RouterInit): Router {
|
|
|
2444
2391
|
) {
|
|
2445
2392
|
savedScrollPositions = positions;
|
|
2446
2393
|
getScrollPosition = getPosition;
|
|
2447
|
-
getScrollRestorationKey = getKey ||
|
|
2394
|
+
getScrollRestorationKey = getKey || null;
|
|
2448
2395
|
|
|
2449
2396
|
// Perform initial hydration scroll restoration, since we miss the boat on
|
|
2450
2397
|
// the initial updateState() because we've not yet rendered <ScrollRestoration/>
|
|
@@ -2464,15 +2411,23 @@ export function createRouter(init: RouterInit): Router {
|
|
|
2464
2411
|
};
|
|
2465
2412
|
}
|
|
2466
2413
|
|
|
2414
|
+
function getScrollKey(location: Location, matches: AgnosticDataRouteMatch[]) {
|
|
2415
|
+
if (getScrollRestorationKey) {
|
|
2416
|
+
let key = getScrollRestorationKey(
|
|
2417
|
+
location,
|
|
2418
|
+
matches.map((m) => createUseMatchesMatch(m, state.loaderData))
|
|
2419
|
+
);
|
|
2420
|
+
return key || location.key;
|
|
2421
|
+
}
|
|
2422
|
+
return location.key;
|
|
2423
|
+
}
|
|
2424
|
+
|
|
2467
2425
|
function saveScrollPosition(
|
|
2468
2426
|
location: Location,
|
|
2469
2427
|
matches: AgnosticDataRouteMatch[]
|
|
2470
2428
|
): void {
|
|
2471
|
-
if (savedScrollPositions &&
|
|
2472
|
-
let
|
|
2473
|
-
createUseMatchesMatch(m, state.loaderData)
|
|
2474
|
-
);
|
|
2475
|
-
let key = getScrollRestorationKey(location, userMatches) || location.key;
|
|
2429
|
+
if (savedScrollPositions && getScrollPosition) {
|
|
2430
|
+
let key = getScrollKey(location, matches);
|
|
2476
2431
|
savedScrollPositions[key] = getScrollPosition();
|
|
2477
2432
|
}
|
|
2478
2433
|
}
|
|
@@ -2481,11 +2436,8 @@ export function createRouter(init: RouterInit): Router {
|
|
|
2481
2436
|
location: Location,
|
|
2482
2437
|
matches: AgnosticDataRouteMatch[]
|
|
2483
2438
|
): number | null {
|
|
2484
|
-
if (savedScrollPositions
|
|
2485
|
-
let
|
|
2486
|
-
createUseMatchesMatch(m, state.loaderData)
|
|
2487
|
-
);
|
|
2488
|
-
let key = getScrollRestorationKey(location, userMatches) || location.key;
|
|
2439
|
+
if (savedScrollPositions) {
|
|
2440
|
+
let key = getScrollKey(location, matches);
|
|
2489
2441
|
let y = savedScrollPositions[key];
|
|
2490
2442
|
if (typeof y === "number") {
|
|
2491
2443
|
return y;
|
|
@@ -2840,9 +2792,7 @@ export function createStaticHandler(
|
|
|
2840
2792
|
manifest,
|
|
2841
2793
|
mapRouteProperties,
|
|
2842
2794
|
basename,
|
|
2843
|
-
true,
|
|
2844
|
-
isRouteRequest,
|
|
2845
|
-
requestContext
|
|
2795
|
+
{ isStaticRequest: true, isRouteRequest, requestContext }
|
|
2846
2796
|
);
|
|
2847
2797
|
|
|
2848
2798
|
if (request.signal.aborted) {
|
|
@@ -3008,9 +2958,7 @@ export function createStaticHandler(
|
|
|
3008
2958
|
manifest,
|
|
3009
2959
|
mapRouteProperties,
|
|
3010
2960
|
basename,
|
|
3011
|
-
true,
|
|
3012
|
-
isRouteRequest,
|
|
3013
|
-
requestContext
|
|
2961
|
+
{ isStaticRequest: true, isRouteRequest, requestContext }
|
|
3014
2962
|
)
|
|
3015
2963
|
),
|
|
3016
2964
|
]);
|
|
@@ -3085,7 +3033,11 @@ export function getStaticContextFromError(
|
|
|
3085
3033
|
function isSubmissionNavigation(
|
|
3086
3034
|
opts: RouterNavigateOptions
|
|
3087
3035
|
): opts is SubmissionNavigateOptions {
|
|
3088
|
-
return
|
|
3036
|
+
return (
|
|
3037
|
+
opts != null &&
|
|
3038
|
+
(("formData" in opts && opts.formData != null) ||
|
|
3039
|
+
("body" in opts && opts.body !== undefined))
|
|
3040
|
+
);
|
|
3089
3041
|
}
|
|
3090
3042
|
|
|
3091
3043
|
function normalizeTo(
|
|
@@ -3181,28 +3133,120 @@ function normalizeNavigateOptions(
|
|
|
3181
3133
|
};
|
|
3182
3134
|
}
|
|
3183
3135
|
|
|
3136
|
+
let getInvalidBodyError = () => ({
|
|
3137
|
+
path,
|
|
3138
|
+
error: getInternalRouterError(400, { type: "invalid-body" }),
|
|
3139
|
+
});
|
|
3140
|
+
|
|
3184
3141
|
// Create a Submission on non-GET navigations
|
|
3185
|
-
let
|
|
3186
|
-
|
|
3187
|
-
|
|
3188
|
-
|
|
3189
|
-
|
|
3190
|
-
|
|
3191
|
-
|
|
3192
|
-
|
|
3193
|
-
|
|
3194
|
-
|
|
3195
|
-
|
|
3196
|
-
|
|
3142
|
+
let rawFormMethod = opts.formMethod || "get";
|
|
3143
|
+
let formMethod = normalizeFormMethod
|
|
3144
|
+
? (rawFormMethod.toUpperCase() as V7_FormMethod)
|
|
3145
|
+
: (rawFormMethod.toLowerCase() as FormMethod);
|
|
3146
|
+
let formAction = stripHashFromPath(path);
|
|
3147
|
+
|
|
3148
|
+
if (opts.body !== undefined) {
|
|
3149
|
+
if (opts.formEncType === "text/plain") {
|
|
3150
|
+
// text only support POST/PUT/PATCH/DELETE submissions
|
|
3151
|
+
if (!isMutationMethod(formMethod)) {
|
|
3152
|
+
return getInvalidBodyError();
|
|
3153
|
+
}
|
|
3154
|
+
|
|
3155
|
+
let text =
|
|
3156
|
+
typeof opts.body === "string"
|
|
3157
|
+
? opts.body
|
|
3158
|
+
: opts.body instanceof FormData ||
|
|
3159
|
+
opts.body instanceof URLSearchParams
|
|
3160
|
+
? // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#plain-text-form-data
|
|
3161
|
+
Array.from(opts.body.entries()).reduce(
|
|
3162
|
+
(acc, [name, value]) => `${acc}${name}=${value}\n`,
|
|
3163
|
+
""
|
|
3164
|
+
)
|
|
3165
|
+
: String(opts.body);
|
|
3197
3166
|
|
|
3198
|
-
|
|
3199
|
-
|
|
3167
|
+
return {
|
|
3168
|
+
path,
|
|
3169
|
+
submission: {
|
|
3170
|
+
formMethod,
|
|
3171
|
+
formAction,
|
|
3172
|
+
formEncType: opts.formEncType,
|
|
3173
|
+
formData: undefined,
|
|
3174
|
+
json: undefined,
|
|
3175
|
+
text,
|
|
3176
|
+
},
|
|
3177
|
+
};
|
|
3178
|
+
} else if (opts.formEncType === "application/json") {
|
|
3179
|
+
// json only supports POST/PUT/PATCH/DELETE submissions
|
|
3180
|
+
if (!isMutationMethod(formMethod)) {
|
|
3181
|
+
return getInvalidBodyError();
|
|
3182
|
+
}
|
|
3183
|
+
|
|
3184
|
+
try {
|
|
3185
|
+
let json =
|
|
3186
|
+
typeof opts.body === "string" ? JSON.parse(opts.body) : opts.body;
|
|
3187
|
+
|
|
3188
|
+
return {
|
|
3189
|
+
path,
|
|
3190
|
+
submission: {
|
|
3191
|
+
formMethod,
|
|
3192
|
+
formAction,
|
|
3193
|
+
formEncType: opts.formEncType,
|
|
3194
|
+
formData: undefined,
|
|
3195
|
+
json,
|
|
3196
|
+
text: undefined,
|
|
3197
|
+
},
|
|
3198
|
+
};
|
|
3199
|
+
} catch (e) {
|
|
3200
|
+
return getInvalidBodyError();
|
|
3201
|
+
}
|
|
3200
3202
|
}
|
|
3201
3203
|
}
|
|
3202
3204
|
|
|
3205
|
+
invariant(
|
|
3206
|
+
typeof FormData === "function",
|
|
3207
|
+
"FormData is not available in this environment"
|
|
3208
|
+
);
|
|
3209
|
+
|
|
3210
|
+
let searchParams: URLSearchParams;
|
|
3211
|
+
let formData: FormData;
|
|
3212
|
+
|
|
3213
|
+
if (opts.formData) {
|
|
3214
|
+
searchParams = convertFormDataToSearchParams(opts.formData);
|
|
3215
|
+
formData = opts.formData;
|
|
3216
|
+
} else if (opts.body instanceof FormData) {
|
|
3217
|
+
searchParams = convertFormDataToSearchParams(opts.body);
|
|
3218
|
+
formData = opts.body;
|
|
3219
|
+
} else if (opts.body instanceof URLSearchParams) {
|
|
3220
|
+
searchParams = opts.body;
|
|
3221
|
+
formData = convertSearchParamsToFormData(searchParams);
|
|
3222
|
+
} else if (opts.body == null) {
|
|
3223
|
+
searchParams = new URLSearchParams();
|
|
3224
|
+
formData = new FormData();
|
|
3225
|
+
} else {
|
|
3226
|
+
try {
|
|
3227
|
+
searchParams = new URLSearchParams(opts.body);
|
|
3228
|
+
formData = convertSearchParamsToFormData(searchParams);
|
|
3229
|
+
} catch (e) {
|
|
3230
|
+
return getInvalidBodyError();
|
|
3231
|
+
}
|
|
3232
|
+
}
|
|
3233
|
+
|
|
3234
|
+
let submission: Submission = {
|
|
3235
|
+
formMethod,
|
|
3236
|
+
formAction,
|
|
3237
|
+
formEncType:
|
|
3238
|
+
(opts && opts.formEncType) || "application/x-www-form-urlencoded",
|
|
3239
|
+
formData,
|
|
3240
|
+
json: undefined,
|
|
3241
|
+
text: undefined,
|
|
3242
|
+
};
|
|
3243
|
+
|
|
3244
|
+
if (isMutationMethod(submission.formMethod)) {
|
|
3245
|
+
return { path, submission };
|
|
3246
|
+
}
|
|
3247
|
+
|
|
3203
3248
|
// Flatten submission onto URLSearchParams for GET submissions
|
|
3204
3249
|
let parsedPath = parsePath(path);
|
|
3205
|
-
let searchParams = convertFormDataToSearchParams(opts.formData);
|
|
3206
3250
|
// On GET navigation submissions we can drop the ?index param from the
|
|
3207
3251
|
// resulting location since all loaders will run. But fetcher GET submissions
|
|
3208
3252
|
// only run a single loader so we need to preserve any incoming ?index params
|
|
@@ -3240,6 +3284,7 @@ function getMatchesToLoad(
|
|
|
3240
3284
|
cancelledDeferredRoutes: string[],
|
|
3241
3285
|
cancelledFetcherLoads: string[],
|
|
3242
3286
|
fetchLoadMatches: Map<string, FetchLoadMatch>,
|
|
3287
|
+
fetchRedirectIds: Set<string>,
|
|
3243
3288
|
routesToUse: AgnosticDataRouteObject[],
|
|
3244
3289
|
basename: string | undefined,
|
|
3245
3290
|
pendingActionData?: RouteData,
|
|
@@ -3325,34 +3370,38 @@ function getMatchesToLoad(
|
|
|
3325
3370
|
return;
|
|
3326
3371
|
}
|
|
3327
3372
|
|
|
3373
|
+
// Revalidating fetchers are decoupled from the route matches since they
|
|
3374
|
+
// load from a static href. They only set `defaultShouldRevalidate` on
|
|
3375
|
+
// explicit revalidation due to submission, useRevalidator, or X-Remix-Revalidate
|
|
3376
|
+
//
|
|
3377
|
+
// They automatically revalidate without even calling shouldRevalidate if:
|
|
3378
|
+
// - They were cancelled
|
|
3379
|
+
// - They're in the middle of their first load and therefore this is still
|
|
3380
|
+
// an initial load and not a revalidation
|
|
3381
|
+
//
|
|
3382
|
+
// If neither of those is true, then they _always_ check shouldRevalidate
|
|
3383
|
+
let fetcher = state.fetchers.get(key);
|
|
3384
|
+
let isPerformingInitialLoad =
|
|
3385
|
+
fetcher &&
|
|
3386
|
+
fetcher.state !== "idle" &&
|
|
3387
|
+
fetcher.data === undefined &&
|
|
3388
|
+
// If a fetcher.load redirected then it'll be "loading" without any data
|
|
3389
|
+
// so ensure we're not processing the redirect from this fetcher
|
|
3390
|
+
!fetchRedirectIds.has(key);
|
|
3328
3391
|
let fetcherMatch = getTargetMatch(fetcherMatches, f.path);
|
|
3329
|
-
|
|
3330
|
-
|
|
3331
|
-
|
|
3332
|
-
|
|
3333
|
-
|
|
3334
|
-
|
|
3335
|
-
|
|
3336
|
-
|
|
3337
|
-
|
|
3392
|
+
let shouldRevalidate =
|
|
3393
|
+
cancelledFetcherLoads.includes(key) ||
|
|
3394
|
+
isPerformingInitialLoad ||
|
|
3395
|
+
shouldRevalidateLoader(fetcherMatch, {
|
|
3396
|
+
currentUrl,
|
|
3397
|
+
currentParams: state.matches[state.matches.length - 1].params,
|
|
3398
|
+
nextUrl,
|
|
3399
|
+
nextParams: matches[matches.length - 1].params,
|
|
3400
|
+
...submission,
|
|
3401
|
+
actionResult,
|
|
3402
|
+
defaultShouldRevalidate: isRevalidationRequired,
|
|
3338
3403
|
});
|
|
3339
|
-
return;
|
|
3340
|
-
}
|
|
3341
3404
|
|
|
3342
|
-
// Revalidating fetchers are decoupled from the route matches since they
|
|
3343
|
-
// hit a static href, so they _always_ check shouldRevalidate and the
|
|
3344
|
-
// default is strictly if a revalidation is explicitly required (action
|
|
3345
|
-
// submissions, useRevalidator, X-Remix-Revalidate).
|
|
3346
|
-
let shouldRevalidate = shouldRevalidateLoader(fetcherMatch, {
|
|
3347
|
-
currentUrl,
|
|
3348
|
-
currentParams: state.matches[state.matches.length - 1].params,
|
|
3349
|
-
nextUrl,
|
|
3350
|
-
nextParams: matches[matches.length - 1].params,
|
|
3351
|
-
...submission,
|
|
3352
|
-
actionResult,
|
|
3353
|
-
// Forced revalidation due to submission, useRevalidator, or X-Remix-Revalidate
|
|
3354
|
-
defaultShouldRevalidate: isRevalidationRequired,
|
|
3355
|
-
});
|
|
3356
3405
|
if (shouldRevalidate) {
|
|
3357
3406
|
revalidatingFetchers.push({
|
|
3358
3407
|
key,
|
|
@@ -3503,9 +3552,11 @@ async function callLoaderOrAction(
|
|
|
3503
3552
|
manifest: RouteManifest,
|
|
3504
3553
|
mapRouteProperties: MapRoutePropertiesFunction,
|
|
3505
3554
|
basename: string,
|
|
3506
|
-
|
|
3507
|
-
|
|
3508
|
-
|
|
3555
|
+
opts: {
|
|
3556
|
+
isStaticRequest?: boolean;
|
|
3557
|
+
isRouteRequest?: boolean;
|
|
3558
|
+
requestContext?: unknown;
|
|
3559
|
+
} = {}
|
|
3509
3560
|
): Promise<DataResult> {
|
|
3510
3561
|
let resultType;
|
|
3511
3562
|
let result;
|
|
@@ -3518,7 +3569,11 @@ async function callLoaderOrAction(
|
|
|
3518
3569
|
onReject = () => reject();
|
|
3519
3570
|
request.signal.addEventListener("abort", onReject);
|
|
3520
3571
|
return Promise.race([
|
|
3521
|
-
handler({
|
|
3572
|
+
handler({
|
|
3573
|
+
request,
|
|
3574
|
+
params: match.params,
|
|
3575
|
+
context: opts.requestContext,
|
|
3576
|
+
}),
|
|
3522
3577
|
abortPromise,
|
|
3523
3578
|
]);
|
|
3524
3579
|
};
|
|
@@ -3603,7 +3658,7 @@ async function callLoaderOrAction(
|
|
|
3603
3658
|
true,
|
|
3604
3659
|
location
|
|
3605
3660
|
);
|
|
3606
|
-
} else if (!isStaticRequest) {
|
|
3661
|
+
} else if (!opts.isStaticRequest) {
|
|
3607
3662
|
// Strip off the protocol+origin for same-origin + same-basename absolute
|
|
3608
3663
|
// redirects. If this is a static request, we can let it go back to the
|
|
3609
3664
|
// browser as-is
|
|
@@ -3621,7 +3676,7 @@ async function callLoaderOrAction(
|
|
|
3621
3676
|
// Instead, throw the Response and let the server handle it with an HTTP
|
|
3622
3677
|
// redirect. We also update the Location header in place in this flow so
|
|
3623
3678
|
// basename and relative routing is taken into account
|
|
3624
|
-
if (isStaticRequest) {
|
|
3679
|
+
if (opts.isStaticRequest) {
|
|
3625
3680
|
result.headers.set("Location", location);
|
|
3626
3681
|
throw result;
|
|
3627
3682
|
}
|
|
@@ -3637,7 +3692,7 @@ async function callLoaderOrAction(
|
|
|
3637
3692
|
// For SSR single-route requests, we want to hand Responses back directly
|
|
3638
3693
|
// without unwrapping. We do this with the QueryRouteResponse wrapper
|
|
3639
3694
|
// interface so we can know whether it was returned or thrown
|
|
3640
|
-
if (isRouteRequest) {
|
|
3695
|
+
if (opts.isRouteRequest) {
|
|
3641
3696
|
// eslint-disable-next-line no-throw-literal
|
|
3642
3697
|
throw {
|
|
3643
3698
|
type: resultType || ResultType.data,
|
|
@@ -3700,18 +3755,30 @@ function createClientSideRequest(
|
|
|
3700
3755
|
let init: RequestInit = { signal };
|
|
3701
3756
|
|
|
3702
3757
|
if (submission && isMutationMethod(submission.formMethod)) {
|
|
3703
|
-
let { formMethod, formEncType
|
|
3758
|
+
let { formMethod, formEncType } = submission;
|
|
3704
3759
|
// Didn't think we needed this but it turns out unlike other methods, patch
|
|
3705
3760
|
// won't be properly normalized to uppercase and results in a 405 error.
|
|
3706
3761
|
// See: https://fetch.spec.whatwg.org/#concept-method
|
|
3707
3762
|
init.method = formMethod.toUpperCase();
|
|
3708
|
-
|
|
3709
|
-
|
|
3710
|
-
|
|
3711
|
-
|
|
3763
|
+
|
|
3764
|
+
if (formEncType === "application/json") {
|
|
3765
|
+
init.headers = new Headers({ "Content-Type": formEncType });
|
|
3766
|
+
init.body = JSON.stringify(submission.json);
|
|
3767
|
+
} else if (formEncType === "text/plain") {
|
|
3768
|
+
// Content-Type is inferred (https://fetch.spec.whatwg.org/#dom-request)
|
|
3769
|
+
init.body = submission.text;
|
|
3770
|
+
} else if (
|
|
3771
|
+
formEncType === "application/x-www-form-urlencoded" &&
|
|
3772
|
+
submission.formData
|
|
3773
|
+
) {
|
|
3774
|
+
// Content-Type is inferred (https://fetch.spec.whatwg.org/#dom-request)
|
|
3775
|
+
init.body = convertFormDataToSearchParams(submission.formData);
|
|
3776
|
+
} else {
|
|
3777
|
+
// Content-Type is inferred (https://fetch.spec.whatwg.org/#dom-request)
|
|
3778
|
+
init.body = submission.formData;
|
|
3779
|
+
}
|
|
3712
3780
|
}
|
|
3713
3781
|
|
|
3714
|
-
// Content-Type is inferred (https://fetch.spec.whatwg.org/#dom-request)
|
|
3715
3782
|
return new Request(url, init);
|
|
3716
3783
|
}
|
|
3717
3784
|
|
|
@@ -3720,12 +3787,22 @@ function convertFormDataToSearchParams(formData: FormData): URLSearchParams {
|
|
|
3720
3787
|
|
|
3721
3788
|
for (let [key, value] of formData.entries()) {
|
|
3722
3789
|
// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#converting-an-entry-list-to-a-list-of-name-value-pairs
|
|
3723
|
-
searchParams.append(key, value
|
|
3790
|
+
searchParams.append(key, typeof value === "string" ? value : value.name);
|
|
3724
3791
|
}
|
|
3725
3792
|
|
|
3726
3793
|
return searchParams;
|
|
3727
3794
|
}
|
|
3728
3795
|
|
|
3796
|
+
function convertSearchParamsToFormData(
|
|
3797
|
+
searchParams: URLSearchParams
|
|
3798
|
+
): FormData {
|
|
3799
|
+
let formData = new FormData();
|
|
3800
|
+
for (let [key, value] of searchParams.entries()) {
|
|
3801
|
+
formData.append(key, value);
|
|
3802
|
+
}
|
|
3803
|
+
return formData;
|
|
3804
|
+
}
|
|
3805
|
+
|
|
3729
3806
|
function processRouteLoaderData(
|
|
3730
3807
|
matches: AgnosticDataRouteMatch[],
|
|
3731
3808
|
matchesToLoad: AgnosticDataRouteMatch[],
|
|
@@ -3877,15 +3954,7 @@ function processLoaderData(
|
|
|
3877
3954
|
// in resolveDeferredResults
|
|
3878
3955
|
invariant(false, "Unhandled fetcher deferred data");
|
|
3879
3956
|
} else {
|
|
3880
|
-
let doneFetcher
|
|
3881
|
-
state: "idle",
|
|
3882
|
-
data: result.data,
|
|
3883
|
-
formMethod: undefined,
|
|
3884
|
-
formAction: undefined,
|
|
3885
|
-
formEncType: undefined,
|
|
3886
|
-
formData: undefined,
|
|
3887
|
-
" _hasFetcherDoneAnything ": true,
|
|
3888
|
-
};
|
|
3957
|
+
let doneFetcher = getDoneFetcher(result.data);
|
|
3889
3958
|
state.fetchers.set(key, doneFetcher);
|
|
3890
3959
|
}
|
|
3891
3960
|
}
|
|
@@ -3973,7 +4042,7 @@ function getInternalRouterError(
|
|
|
3973
4042
|
pathname?: string;
|
|
3974
4043
|
routeId?: string;
|
|
3975
4044
|
method?: string;
|
|
3976
|
-
type?: "defer-action";
|
|
4045
|
+
type?: "defer-action" | "invalid-body";
|
|
3977
4046
|
} = {}
|
|
3978
4047
|
) {
|
|
3979
4048
|
let statusText = "Unknown Server Error";
|
|
@@ -3988,6 +4057,8 @@ function getInternalRouterError(
|
|
|
3988
4057
|
`so there is no way to handle the request.`;
|
|
3989
4058
|
} else if (type === "defer-action") {
|
|
3990
4059
|
errorMessage = "defer() is not supported in actions";
|
|
4060
|
+
} else if (type === "invalid-body") {
|
|
4061
|
+
errorMessage = "Unable to encode submission body";
|
|
3991
4062
|
}
|
|
3992
4063
|
} else if (status === 403) {
|
|
3993
4064
|
statusText = "Forbidden";
|
|
@@ -4226,4 +4297,157 @@ function getTargetMatch(
|
|
|
4226
4297
|
let pathMatches = getPathContributingMatches(matches);
|
|
4227
4298
|
return pathMatches[pathMatches.length - 1];
|
|
4228
4299
|
}
|
|
4300
|
+
|
|
4301
|
+
function getSubmissionFromNavigation(
|
|
4302
|
+
navigation: Navigation
|
|
4303
|
+
): Submission | undefined {
|
|
4304
|
+
let { formMethod, formAction, formEncType, text, formData, json } =
|
|
4305
|
+
navigation;
|
|
4306
|
+
if (!formMethod || !formAction || !formEncType) {
|
|
4307
|
+
return;
|
|
4308
|
+
}
|
|
4309
|
+
|
|
4310
|
+
if (text != null) {
|
|
4311
|
+
return {
|
|
4312
|
+
formMethod,
|
|
4313
|
+
formAction,
|
|
4314
|
+
formEncType,
|
|
4315
|
+
formData: undefined,
|
|
4316
|
+
json: undefined,
|
|
4317
|
+
text,
|
|
4318
|
+
};
|
|
4319
|
+
} else if (formData != null) {
|
|
4320
|
+
return {
|
|
4321
|
+
formMethod,
|
|
4322
|
+
formAction,
|
|
4323
|
+
formEncType,
|
|
4324
|
+
formData,
|
|
4325
|
+
json: undefined,
|
|
4326
|
+
text: undefined,
|
|
4327
|
+
};
|
|
4328
|
+
} else if (json !== undefined) {
|
|
4329
|
+
return {
|
|
4330
|
+
formMethod,
|
|
4331
|
+
formAction,
|
|
4332
|
+
formEncType,
|
|
4333
|
+
formData: undefined,
|
|
4334
|
+
json,
|
|
4335
|
+
text: undefined,
|
|
4336
|
+
};
|
|
4337
|
+
}
|
|
4338
|
+
}
|
|
4339
|
+
|
|
4340
|
+
function getLoadingNavigation(
|
|
4341
|
+
location: Location,
|
|
4342
|
+
submission?: Submission
|
|
4343
|
+
): NavigationStates["Loading"] {
|
|
4344
|
+
if (submission) {
|
|
4345
|
+
let navigation: NavigationStates["Loading"] = {
|
|
4346
|
+
state: "loading",
|
|
4347
|
+
location,
|
|
4348
|
+
formMethod: submission.formMethod,
|
|
4349
|
+
formAction: submission.formAction,
|
|
4350
|
+
formEncType: submission.formEncType,
|
|
4351
|
+
formData: submission.formData,
|
|
4352
|
+
json: submission.json,
|
|
4353
|
+
text: submission.text,
|
|
4354
|
+
};
|
|
4355
|
+
return navigation;
|
|
4356
|
+
} else {
|
|
4357
|
+
let navigation: NavigationStates["Loading"] = {
|
|
4358
|
+
state: "loading",
|
|
4359
|
+
location,
|
|
4360
|
+
formMethod: undefined,
|
|
4361
|
+
formAction: undefined,
|
|
4362
|
+
formEncType: undefined,
|
|
4363
|
+
formData: undefined,
|
|
4364
|
+
json: undefined,
|
|
4365
|
+
text: undefined,
|
|
4366
|
+
};
|
|
4367
|
+
return navigation;
|
|
4368
|
+
}
|
|
4369
|
+
}
|
|
4370
|
+
|
|
4371
|
+
function getSubmittingNavigation(
|
|
4372
|
+
location: Location,
|
|
4373
|
+
submission: Submission
|
|
4374
|
+
): NavigationStates["Submitting"] {
|
|
4375
|
+
let navigation: NavigationStates["Submitting"] = {
|
|
4376
|
+
state: "submitting",
|
|
4377
|
+
location,
|
|
4378
|
+
formMethod: submission.formMethod,
|
|
4379
|
+
formAction: submission.formAction,
|
|
4380
|
+
formEncType: submission.formEncType,
|
|
4381
|
+
formData: submission.formData,
|
|
4382
|
+
json: submission.json,
|
|
4383
|
+
text: submission.text,
|
|
4384
|
+
};
|
|
4385
|
+
return navigation;
|
|
4386
|
+
}
|
|
4387
|
+
|
|
4388
|
+
function getLoadingFetcher(
|
|
4389
|
+
submission?: Submission,
|
|
4390
|
+
data?: Fetcher["data"]
|
|
4391
|
+
): FetcherStates["Loading"] {
|
|
4392
|
+
if (submission) {
|
|
4393
|
+
let fetcher: FetcherStates["Loading"] = {
|
|
4394
|
+
state: "loading",
|
|
4395
|
+
formMethod: submission.formMethod,
|
|
4396
|
+
formAction: submission.formAction,
|
|
4397
|
+
formEncType: submission.formEncType,
|
|
4398
|
+
formData: submission.formData,
|
|
4399
|
+
json: submission.json,
|
|
4400
|
+
text: submission.text,
|
|
4401
|
+
data,
|
|
4402
|
+
" _hasFetcherDoneAnything ": true,
|
|
4403
|
+
};
|
|
4404
|
+
return fetcher;
|
|
4405
|
+
} else {
|
|
4406
|
+
let fetcher: FetcherStates["Loading"] = {
|
|
4407
|
+
state: "loading",
|
|
4408
|
+
formMethod: undefined,
|
|
4409
|
+
formAction: undefined,
|
|
4410
|
+
formEncType: undefined,
|
|
4411
|
+
formData: undefined,
|
|
4412
|
+
json: undefined,
|
|
4413
|
+
text: undefined,
|
|
4414
|
+
data,
|
|
4415
|
+
" _hasFetcherDoneAnything ": true,
|
|
4416
|
+
};
|
|
4417
|
+
return fetcher;
|
|
4418
|
+
}
|
|
4419
|
+
}
|
|
4420
|
+
|
|
4421
|
+
function getSubmittingFetcher(
|
|
4422
|
+
submission: Submission,
|
|
4423
|
+
existingFetcher?: Fetcher
|
|
4424
|
+
): FetcherStates["Submitting"] {
|
|
4425
|
+
let fetcher: FetcherStates["Submitting"] = {
|
|
4426
|
+
state: "submitting",
|
|
4427
|
+
formMethod: submission.formMethod,
|
|
4428
|
+
formAction: submission.formAction,
|
|
4429
|
+
formEncType: submission.formEncType,
|
|
4430
|
+
formData: submission.formData,
|
|
4431
|
+
json: submission.json,
|
|
4432
|
+
text: submission.text,
|
|
4433
|
+
data: existingFetcher ? existingFetcher.data : undefined,
|
|
4434
|
+
" _hasFetcherDoneAnything ": true,
|
|
4435
|
+
};
|
|
4436
|
+
return fetcher;
|
|
4437
|
+
}
|
|
4438
|
+
|
|
4439
|
+
function getDoneFetcher(data: Fetcher["data"]): FetcherStates["Idle"] {
|
|
4440
|
+
let fetcher: FetcherStates["Idle"] = {
|
|
4441
|
+
state: "idle",
|
|
4442
|
+
formMethod: undefined,
|
|
4443
|
+
formAction: undefined,
|
|
4444
|
+
formEncType: undefined,
|
|
4445
|
+
formData: undefined,
|
|
4446
|
+
json: undefined,
|
|
4447
|
+
text: undefined,
|
|
4448
|
+
data,
|
|
4449
|
+
" _hasFetcherDoneAnything ": true,
|
|
4450
|
+
};
|
|
4451
|
+
return fetcher;
|
|
4452
|
+
}
|
|
4229
4453
|
//#endregion
|