@remix-run/router 1.1.0 → 1.2.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 +13 -0
- package/dist/router.cjs.js +150 -56
- package/dist/router.cjs.js.map +1 -1
- package/dist/router.d.ts +4 -1
- package/dist/router.js +149 -56
- package/dist/router.js.map +1 -1
- package/dist/router.umd.js +150 -56
- 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/package.json +1 -1
- package/router.ts +152 -47
package/package.json
CHANGED
package/router.ts
CHANGED
|
@@ -435,6 +435,7 @@ type FetcherStates<TData = any> = {
|
|
|
435
435
|
formEncType: undefined;
|
|
436
436
|
formData: undefined;
|
|
437
437
|
data: TData | undefined;
|
|
438
|
+
" _hasFetcherDoneAnything "?: boolean;
|
|
438
439
|
};
|
|
439
440
|
Loading: {
|
|
440
441
|
state: "loading";
|
|
@@ -443,6 +444,7 @@ type FetcherStates<TData = any> = {
|
|
|
443
444
|
formEncType: FormEncType | undefined;
|
|
444
445
|
formData: FormData | undefined;
|
|
445
446
|
data: TData | undefined;
|
|
447
|
+
" _hasFetcherDoneAnything "?: boolean;
|
|
446
448
|
};
|
|
447
449
|
Submitting: {
|
|
448
450
|
state: "submitting";
|
|
@@ -451,6 +453,7 @@ type FetcherStates<TData = any> = {
|
|
|
451
453
|
formEncType: FormEncType;
|
|
452
454
|
formData: FormData;
|
|
453
455
|
data: TData | undefined;
|
|
456
|
+
" _hasFetcherDoneAnything "?: boolean;
|
|
454
457
|
};
|
|
455
458
|
};
|
|
456
459
|
|
|
@@ -593,7 +596,9 @@ export function createRouter(init: RouterInit): Router {
|
|
|
593
596
|
// we don't get the saved positions from <ScrollRestoration /> until _after_
|
|
594
597
|
// the initial render, we need to manually trigger a separate updateState to
|
|
595
598
|
// send along the restoreScrollPosition
|
|
596
|
-
|
|
599
|
+
// Set to true if we have `hydrationData` since we assume we were SSR'd and that
|
|
600
|
+
// SSR did the initial scroll restoration.
|
|
601
|
+
let initialScrollRestored = init.hydrationData != null;
|
|
597
602
|
|
|
598
603
|
let initialMatches = matchRoutes(
|
|
599
604
|
dataRoutes,
|
|
@@ -623,7 +628,8 @@ export function createRouter(init: RouterInit): Router {
|
|
|
623
628
|
matches: initialMatches,
|
|
624
629
|
initialized,
|
|
625
630
|
navigation: IDLE_NAVIGATION,
|
|
626
|
-
|
|
631
|
+
// Don't restore on initial updateState() if we were SSR'd
|
|
632
|
+
restoreScrollPosition: init.hydrationData != null ? false : null,
|
|
627
633
|
preventScrollReset: false,
|
|
628
634
|
revalidation: "idle",
|
|
629
635
|
loaderData: (init.hydrationData && init.hydrationData.loaderData) || {},
|
|
@@ -741,24 +747,36 @@ export function createRouter(init: RouterInit): Router {
|
|
|
741
747
|
state.navigation.state === "loading" &&
|
|
742
748
|
state.navigation.formAction?.split("?")[0] === location.pathname;
|
|
743
749
|
|
|
750
|
+
let actionData: RouteData | null;
|
|
751
|
+
if (newState.actionData) {
|
|
752
|
+
if (Object.keys(newState.actionData).length > 0) {
|
|
753
|
+
actionData = newState.actionData;
|
|
754
|
+
} else {
|
|
755
|
+
// Empty actionData -> clear prior actionData due to an action error
|
|
756
|
+
actionData = null;
|
|
757
|
+
}
|
|
758
|
+
} else if (isActionReload) {
|
|
759
|
+
// Keep the current data if we're wrapping up the action reload
|
|
760
|
+
actionData = state.actionData;
|
|
761
|
+
} else {
|
|
762
|
+
// Clear actionData on any other completed navigations
|
|
763
|
+
actionData = null;
|
|
764
|
+
}
|
|
765
|
+
|
|
744
766
|
// Always preserve any existing loaderData from re-used routes
|
|
745
|
-
let
|
|
746
|
-
?
|
|
747
|
-
loaderData
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
: {};
|
|
767
|
+
let loaderData = newState.loaderData
|
|
768
|
+
? mergeLoaderData(
|
|
769
|
+
state.loaderData,
|
|
770
|
+
newState.loaderData,
|
|
771
|
+
newState.matches || [],
|
|
772
|
+
newState.errors
|
|
773
|
+
)
|
|
774
|
+
: state.loaderData;
|
|
754
775
|
|
|
755
776
|
updateState({
|
|
756
|
-
//
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
...(isActionReload ? {} : { actionData: null }),
|
|
760
|
-
...newState,
|
|
761
|
-
...newLoaderData,
|
|
777
|
+
...newState, // matches, errors, fetchers go through as-is
|
|
778
|
+
actionData,
|
|
779
|
+
loaderData,
|
|
762
780
|
historyAction: pendingAction,
|
|
763
781
|
location,
|
|
764
782
|
initialized: true,
|
|
@@ -815,11 +833,26 @@ export function createRouter(init: RouterInit): Router {
|
|
|
815
833
|
...init.history.encodeLocation(location),
|
|
816
834
|
};
|
|
817
835
|
|
|
818
|
-
let
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
836
|
+
let userReplace = opts && opts.replace != null ? opts.replace : undefined;
|
|
837
|
+
|
|
838
|
+
let historyAction = HistoryAction.Push;
|
|
839
|
+
|
|
840
|
+
if (userReplace === true) {
|
|
841
|
+
historyAction = HistoryAction.Replace;
|
|
842
|
+
} else if (userReplace === false) {
|
|
843
|
+
// no-op
|
|
844
|
+
} else if (
|
|
845
|
+
submission != null &&
|
|
846
|
+
isMutationMethod(submission.formMethod) &&
|
|
847
|
+
submission.formAction === state.location.pathname + state.location.search
|
|
848
|
+
) {
|
|
849
|
+
// By default on submissions to the current location we REPLACE so that
|
|
850
|
+
// users don't have to double-click the back button to get to the prior
|
|
851
|
+
// location. If the user redirects to a different location from the
|
|
852
|
+
// action/loader this will be ignored and the redirect will be a PUSH
|
|
853
|
+
historyAction = HistoryAction.Replace;
|
|
854
|
+
}
|
|
855
|
+
|
|
823
856
|
let preventScrollReset =
|
|
824
857
|
opts && "preventScrollReset" in opts
|
|
825
858
|
? opts.preventScrollReset === true
|
|
@@ -996,6 +1029,7 @@ export function createRouter(init: RouterInit): Router {
|
|
|
996
1029
|
|
|
997
1030
|
completeNavigation(location, {
|
|
998
1031
|
matches,
|
|
1032
|
+
...(pendingActionData ? { actionData: pendingActionData } : {}),
|
|
999
1033
|
loaderData,
|
|
1000
1034
|
errors,
|
|
1001
1035
|
});
|
|
@@ -1048,11 +1082,17 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1048
1082
|
}
|
|
1049
1083
|
|
|
1050
1084
|
if (isRedirectResult(result)) {
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1085
|
+
let replace: boolean;
|
|
1086
|
+
if (opts && opts.replace != null) {
|
|
1087
|
+
replace = opts.replace;
|
|
1088
|
+
} else {
|
|
1089
|
+
// If the user didn't explicity indicate replace behavior, replace if
|
|
1090
|
+
// we redirected to the exact same location we're currently at to avoid
|
|
1091
|
+
// double back-buttons
|
|
1092
|
+
replace =
|
|
1093
|
+
result.location === state.location.pathname + state.location.search;
|
|
1094
|
+
}
|
|
1095
|
+
await startRedirectNavigation(state, result, replace);
|
|
1056
1096
|
return { shortCircuited: true };
|
|
1057
1097
|
}
|
|
1058
1098
|
|
|
@@ -1070,6 +1110,8 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1070
1110
|
}
|
|
1071
1111
|
|
|
1072
1112
|
return {
|
|
1113
|
+
// Send back an empty object we can use to clear out any prior actionData
|
|
1114
|
+
pendingActionData: {},
|
|
1073
1115
|
pendingActionError: { [boundaryMatch.route.id]: result.error },
|
|
1074
1116
|
};
|
|
1075
1117
|
}
|
|
@@ -1136,10 +1178,10 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1136
1178
|
if (matchesToLoad.length === 0 && revalidatingFetchers.length === 0) {
|
|
1137
1179
|
completeNavigation(location, {
|
|
1138
1180
|
matches,
|
|
1139
|
-
loaderData:
|
|
1181
|
+
loaderData: {},
|
|
1140
1182
|
// Commit pending error if we're short circuiting
|
|
1141
1183
|
errors: pendingError || null,
|
|
1142
|
-
actionData: pendingActionData
|
|
1184
|
+
...(pendingActionData ? { actionData: pendingActionData } : {}),
|
|
1143
1185
|
});
|
|
1144
1186
|
return { shortCircuited: true };
|
|
1145
1187
|
}
|
|
@@ -1158,12 +1200,18 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1158
1200
|
formAction: undefined,
|
|
1159
1201
|
formEncType: undefined,
|
|
1160
1202
|
formData: undefined,
|
|
1203
|
+
" _hasFetcherDoneAnything ": true,
|
|
1161
1204
|
};
|
|
1162
1205
|
state.fetchers.set(key, revalidatingFetcher);
|
|
1163
1206
|
});
|
|
1207
|
+
let actionData = pendingActionData || state.actionData;
|
|
1164
1208
|
updateState({
|
|
1165
1209
|
navigation: loadingNavigation,
|
|
1166
|
-
actionData
|
|
1210
|
+
...(actionData
|
|
1211
|
+
? Object.keys(actionData).length === 0
|
|
1212
|
+
? { actionData: null }
|
|
1213
|
+
: { actionData }
|
|
1214
|
+
: {}),
|
|
1167
1215
|
...(revalidatingFetchers.length > 0
|
|
1168
1216
|
? { fetchers: new Map(state.fetchers) }
|
|
1169
1217
|
: {}),
|
|
@@ -1310,6 +1358,7 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1310
1358
|
state: "submitting",
|
|
1311
1359
|
...submission,
|
|
1312
1360
|
data: existingFetcher && existingFetcher.data,
|
|
1361
|
+
" _hasFetcherDoneAnything ": true,
|
|
1313
1362
|
};
|
|
1314
1363
|
state.fetchers.set(key, fetcher);
|
|
1315
1364
|
updateState({ fetchers: new Map(state.fetchers) });
|
|
@@ -1347,11 +1396,12 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1347
1396
|
state: "loading",
|
|
1348
1397
|
...submission,
|
|
1349
1398
|
data: undefined,
|
|
1399
|
+
" _hasFetcherDoneAnything ": true,
|
|
1350
1400
|
};
|
|
1351
1401
|
state.fetchers.set(key, loadingFetcher);
|
|
1352
1402
|
updateState({ fetchers: new Map(state.fetchers) });
|
|
1353
1403
|
|
|
1354
|
-
return startRedirectNavigation(state, actionResult);
|
|
1404
|
+
return startRedirectNavigation(state, actionResult, false, true);
|
|
1355
1405
|
}
|
|
1356
1406
|
|
|
1357
1407
|
// Process any non-redirect errors thrown
|
|
@@ -1385,6 +1435,7 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1385
1435
|
state: "loading",
|
|
1386
1436
|
data: actionResult.data,
|
|
1387
1437
|
...submission,
|
|
1438
|
+
" _hasFetcherDoneAnything ": true,
|
|
1388
1439
|
};
|
|
1389
1440
|
state.fetchers.set(key, loadFetcher);
|
|
1390
1441
|
|
|
@@ -1415,6 +1466,7 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1415
1466
|
formAction: undefined,
|
|
1416
1467
|
formEncType: undefined,
|
|
1417
1468
|
formData: undefined,
|
|
1469
|
+
" _hasFetcherDoneAnything ": true,
|
|
1418
1470
|
};
|
|
1419
1471
|
state.fetchers.set(staleKey, revalidatingFetcher);
|
|
1420
1472
|
fetchControllers.set(staleKey, abortController);
|
|
@@ -1465,6 +1517,7 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1465
1517
|
formAction: undefined,
|
|
1466
1518
|
formEncType: undefined,
|
|
1467
1519
|
formData: undefined,
|
|
1520
|
+
" _hasFetcherDoneAnything ": true,
|
|
1468
1521
|
};
|
|
1469
1522
|
state.fetchers.set(key, doneFetcher);
|
|
1470
1523
|
|
|
@@ -1492,7 +1545,12 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1492
1545
|
// manually merge here since we aren't going through completeNavigation
|
|
1493
1546
|
updateState({
|
|
1494
1547
|
errors,
|
|
1495
|
-
loaderData: mergeLoaderData(
|
|
1548
|
+
loaderData: mergeLoaderData(
|
|
1549
|
+
state.loaderData,
|
|
1550
|
+
loaderData,
|
|
1551
|
+
matches,
|
|
1552
|
+
errors
|
|
1553
|
+
),
|
|
1496
1554
|
...(didAbortFetchLoads ? { fetchers: new Map(state.fetchers) } : {}),
|
|
1497
1555
|
});
|
|
1498
1556
|
isRevalidationRequired = false;
|
|
@@ -1518,6 +1576,7 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1518
1576
|
formData: undefined,
|
|
1519
1577
|
...submission,
|
|
1520
1578
|
data: existingFetcher && existingFetcher.data,
|
|
1579
|
+
" _hasFetcherDoneAnything ": true,
|
|
1521
1580
|
};
|
|
1522
1581
|
state.fetchers.set(key, loadingFetcher);
|
|
1523
1582
|
updateState({ fetchers: new Map(state.fetchers) });
|
|
@@ -1586,6 +1645,7 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1586
1645
|
formAction: undefined,
|
|
1587
1646
|
formEncType: undefined,
|
|
1588
1647
|
formData: undefined,
|
|
1648
|
+
" _hasFetcherDoneAnything ": true,
|
|
1589
1649
|
};
|
|
1590
1650
|
state.fetchers.set(key, doneFetcher);
|
|
1591
1651
|
updateState({ fetchers: new Map(state.fetchers) });
|
|
@@ -1613,13 +1673,22 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1613
1673
|
async function startRedirectNavigation(
|
|
1614
1674
|
state: RouterState,
|
|
1615
1675
|
redirect: RedirectResult,
|
|
1616
|
-
replace?: boolean
|
|
1676
|
+
replace?: boolean,
|
|
1677
|
+
isFetchActionRedirect?: boolean
|
|
1617
1678
|
) {
|
|
1618
1679
|
if (redirect.revalidate) {
|
|
1619
1680
|
isRevalidationRequired = true;
|
|
1620
1681
|
}
|
|
1621
1682
|
|
|
1622
|
-
let redirectLocation = createLocation(
|
|
1683
|
+
let redirectLocation = createLocation(
|
|
1684
|
+
state.location,
|
|
1685
|
+
redirect.location,
|
|
1686
|
+
// TODO: This can be removed once we get rid of useTransition in Remix v2
|
|
1687
|
+
{
|
|
1688
|
+
_isRedirect: true,
|
|
1689
|
+
...(isFetchActionRedirect ? { _isFetchActionRedirect: true } : {}),
|
|
1690
|
+
}
|
|
1691
|
+
);
|
|
1623
1692
|
invariant(
|
|
1624
1693
|
redirectLocation,
|
|
1625
1694
|
"Expected a location on the redirect navigation"
|
|
@@ -1782,6 +1851,7 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1782
1851
|
formAction: undefined,
|
|
1783
1852
|
formEncType: undefined,
|
|
1784
1853
|
formData: undefined,
|
|
1854
|
+
" _hasFetcherDoneAnything ": true,
|
|
1785
1855
|
};
|
|
1786
1856
|
state.fetchers.set(key, doneFetcher);
|
|
1787
1857
|
}
|
|
@@ -1928,7 +1998,7 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1928
1998
|
//#region createStaticHandler
|
|
1929
1999
|
////////////////////////////////////////////////////////////////////////////////
|
|
1930
2000
|
|
|
1931
|
-
export function
|
|
2001
|
+
export function createStaticHandler(
|
|
1932
2002
|
routes: AgnosticRouteObject[],
|
|
1933
2003
|
opts?: {
|
|
1934
2004
|
basename?: string;
|
|
@@ -1936,7 +2006,7 @@ export function unstable_createStaticHandler(
|
|
|
1936
2006
|
): StaticHandler {
|
|
1937
2007
|
invariant(
|
|
1938
2008
|
routes.length > 0,
|
|
1939
|
-
"You must provide a non-empty routes array to
|
|
2009
|
+
"You must provide a non-empty routes array to createStaticHandler"
|
|
1940
2010
|
);
|
|
1941
2011
|
|
|
1942
2012
|
let dataRoutes = convertRoutesToDataRoutes(routes);
|
|
@@ -2313,7 +2383,11 @@ export function unstable_createStaticHandler(
|
|
|
2313
2383
|
if (matchesToLoad.length === 0) {
|
|
2314
2384
|
return {
|
|
2315
2385
|
matches,
|
|
2316
|
-
|
|
2386
|
+
// Add a null for all matched routes for proper revalidation on the client
|
|
2387
|
+
loaderData: matches.reduce(
|
|
2388
|
+
(acc, m) => Object.assign(acc, { [m.route.id]: null }),
|
|
2389
|
+
{}
|
|
2390
|
+
),
|
|
2317
2391
|
errors: pendingActionError || null,
|
|
2318
2392
|
statusCode: 200,
|
|
2319
2393
|
loaderHeaders: {},
|
|
@@ -2340,9 +2414,11 @@ export function unstable_createStaticHandler(
|
|
|
2340
2414
|
throw new Error(`${method}() call aborted`);
|
|
2341
2415
|
}
|
|
2342
2416
|
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2417
|
+
let executedLoaders = new Set<string>();
|
|
2418
|
+
results.forEach((result, i) => {
|
|
2419
|
+
executedLoaders.add(matchesToLoad[i].route.id);
|
|
2420
|
+
// Can't do anything with these without the Remix side of things, so just
|
|
2421
|
+
// cancel them for now
|
|
2346
2422
|
if (isDeferredResult(result)) {
|
|
2347
2423
|
result.deferredData.cancel();
|
|
2348
2424
|
}
|
|
@@ -2356,6 +2432,13 @@ export function unstable_createStaticHandler(
|
|
|
2356
2432
|
pendingActionError
|
|
2357
2433
|
);
|
|
2358
2434
|
|
|
2435
|
+
// Add a null for any non-loader matches for proper revalidation on the client
|
|
2436
|
+
matches.forEach((match) => {
|
|
2437
|
+
if (!executedLoaders.has(match.route.id)) {
|
|
2438
|
+
context.loaderData[match.route.id] = null;
|
|
2439
|
+
}
|
|
2440
|
+
});
|
|
2441
|
+
|
|
2359
2442
|
return {
|
|
2360
2443
|
...context,
|
|
2361
2444
|
matches,
|
|
@@ -2498,7 +2581,7 @@ function getMatchesToLoad(
|
|
|
2498
2581
|
? Object.values(pendingError)[0]
|
|
2499
2582
|
: pendingActionData
|
|
2500
2583
|
? Object.values(pendingActionData)[0]
|
|
2501
|
-
:
|
|
2584
|
+
: undefined;
|
|
2502
2585
|
|
|
2503
2586
|
// Pick navigation matches that are net-new or qualify for revalidation
|
|
2504
2587
|
let boundaryId = pendingError ? Object.keys(pendingError)[0] : undefined;
|
|
@@ -2742,7 +2825,9 @@ async function callLoaderOrAction(
|
|
|
2742
2825
|
|
|
2743
2826
|
let data: any;
|
|
2744
2827
|
let contentType = result.headers.get("Content-Type");
|
|
2745
|
-
|
|
2828
|
+
// Check between word boundaries instead of startsWith() due to the last
|
|
2829
|
+
// paragraph of https://httpwg.org/specs/rfc9110.html#field.content-type
|
|
2830
|
+
if (contentType && /\bapplication\/json\b/.test(contentType)) {
|
|
2746
2831
|
data = await result.json();
|
|
2747
2832
|
} else {
|
|
2748
2833
|
data = await result.text();
|
|
@@ -2860,6 +2945,9 @@ function processRouteLoaderData(
|
|
|
2860
2945
|
errors[boundaryMatch.route.id] = error;
|
|
2861
2946
|
}
|
|
2862
2947
|
|
|
2948
|
+
// Clear our any prior loaderData for the throwing route
|
|
2949
|
+
loaderData[id] = undefined;
|
|
2950
|
+
|
|
2863
2951
|
// Once we find our first (highest) error, we set the status code and
|
|
2864
2952
|
// prevent deeper status codes from overriding
|
|
2865
2953
|
if (!foundError) {
|
|
@@ -2893,9 +2981,11 @@ function processRouteLoaderData(
|
|
|
2893
2981
|
});
|
|
2894
2982
|
|
|
2895
2983
|
// If we didn't consume the pending action error (i.e., all loaders
|
|
2896
|
-
// resolved), then consume it here
|
|
2984
|
+
// resolved), then consume it here. Also clear out any loaderData for the
|
|
2985
|
+
// throwing route
|
|
2897
2986
|
if (pendingError) {
|
|
2898
2987
|
errors = pendingError;
|
|
2988
|
+
loaderData[Object.keys(pendingError)[0]] = undefined;
|
|
2899
2989
|
}
|
|
2900
2990
|
|
|
2901
2991
|
return {
|
|
@@ -2962,6 +3052,7 @@ function processLoaderData(
|
|
|
2962
3052
|
formAction: undefined,
|
|
2963
3053
|
formEncType: undefined,
|
|
2964
3054
|
formData: undefined,
|
|
3055
|
+
" _hasFetcherDoneAnything ": true,
|
|
2965
3056
|
};
|
|
2966
3057
|
state.fetchers.set(key, doneFetcher);
|
|
2967
3058
|
}
|
|
@@ -2973,15 +3064,29 @@ function processLoaderData(
|
|
|
2973
3064
|
function mergeLoaderData(
|
|
2974
3065
|
loaderData: RouteData,
|
|
2975
3066
|
newLoaderData: RouteData,
|
|
2976
|
-
matches: AgnosticDataRouteMatch[]
|
|
3067
|
+
matches: AgnosticDataRouteMatch[],
|
|
3068
|
+
errors: RouteData | null | undefined
|
|
2977
3069
|
): RouteData {
|
|
2978
3070
|
let mergedLoaderData = { ...newLoaderData };
|
|
2979
|
-
|
|
3071
|
+
for (let match of matches) {
|
|
2980
3072
|
let id = match.route.id;
|
|
2981
|
-
if (newLoaderData
|
|
3073
|
+
if (newLoaderData.hasOwnProperty(id)) {
|
|
3074
|
+
if (newLoaderData[id] !== undefined) {
|
|
3075
|
+
mergedLoaderData[id] = newLoaderData[id];
|
|
3076
|
+
} else {
|
|
3077
|
+
// No-op - this is so we ignore existing data if we have a key in the
|
|
3078
|
+
// incoming object with an undefined value, which is how we unset a prior
|
|
3079
|
+
// loaderData if we encounter a loader error
|
|
3080
|
+
}
|
|
3081
|
+
} else if (loaderData[id] !== undefined) {
|
|
2982
3082
|
mergedLoaderData[id] = loaderData[id];
|
|
2983
3083
|
}
|
|
2984
|
-
|
|
3084
|
+
|
|
3085
|
+
if (errors && errors.hasOwnProperty(id)) {
|
|
3086
|
+
// Don't keep any loader data below the boundary
|
|
3087
|
+
break;
|
|
3088
|
+
}
|
|
3089
|
+
}
|
|
2985
3090
|
return mergedLoaderData;
|
|
2986
3091
|
}
|
|
2987
3092
|
|