@tanstack/react-router 0.0.1-beta.61 → 0.0.1-beta.63

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.
@@ -89,9 +89,9 @@
89
89
  const previous = this.state;
90
90
  this.state = this.options?.updateFn ? this.options.updateFn(previous)(updater) : updater(previous);
91
91
  if (this.state === previous) return;
92
+ this.options?.onUpdate?.(this.state, previous);
92
93
  this.queue.push(() => {
93
94
  this.listeners.forEach(listener => listener(this.state, previous));
94
- this.options?.onUpdate?.(this.state, previous);
95
95
  });
96
96
  this.#flush();
97
97
  };
@@ -345,6 +345,21 @@
345
345
  function hasObjectPrototype(o) {
346
346
  return Object.prototype.toString.call(o) === '[object Object]';
347
347
  }
348
+ function partialDeepEqual(a, b) {
349
+ if (a === b) {
350
+ return true;
351
+ }
352
+ if (typeof a !== typeof b) {
353
+ return false;
354
+ }
355
+ if (isPlainObject(a) && isPlainObject(b)) {
356
+ return !Object.keys(b).some(key => !partialDeepEqual(a[key], b[key]));
357
+ }
358
+ if (Array.isArray(a) && Array.isArray(b)) {
359
+ return a.length === b.length && a.every((item, index) => partialDeepEqual(item, b[index]));
360
+ }
361
+ return false;
362
+ }
348
363
 
349
364
  function joinPaths(paths) {
350
365
  return cleanPath(paths.filter(Boolean).join('/'));
@@ -577,7 +592,9 @@
577
592
  this.options = options || {};
578
593
  this.isRoot = !options?.getParentRoute;
579
594
  }
580
- init = () => {
595
+ init = opts => {
596
+ this.originalIndex = opts.originalIndex;
597
+ this.router = opts.router;
581
598
  const allOptions = this.options;
582
599
  const isRoot = !allOptions?.path && !allOptions?.id;
583
600
  const parent = this.options?.getParentRoute?.();
@@ -612,19 +629,17 @@
612
629
  this.children = children;
613
630
  return this;
614
631
  };
615
-
616
- // generate: () => {
617
- // invariant(
618
- // false,
619
- // `routeConfig.generate() is used by TanStack Router's file-based routing code generation and should not actually be called during runtime. `,
620
- // )
621
- // },
632
+ generate = options => {
633
+ invariant(false, `route.generate() is used by TanStack Router's file-based routing code generation and should not actually be called during runtime. `);
634
+ };
622
635
  }
623
-
624
636
  class RootRoute extends Route {
625
637
  constructor(options) {
626
638
  super(options);
627
639
  }
640
+ static withRouterContext = () => {
641
+ return options => new RootRoute(options);
642
+ };
628
643
  }
629
644
 
630
645
  // const rootRoute = new RootRoute({
@@ -689,8 +704,13 @@
689
704
  routeSearch: {},
690
705
  search: {},
691
706
  status: 'idle'
707
+ }, {
708
+ onUpdate: next => {
709
+ this.state = next;
710
+ }
692
711
  })
693
712
  });
713
+ this.state = this.store.state;
694
714
  if (!this.#hasLoaders()) {
695
715
  this.store.setState(s => ({
696
716
  ...s,
@@ -698,12 +718,66 @@
698
718
  }));
699
719
  }
700
720
  }
721
+ #hasLoaders = () => {
722
+ return !!(this.route.options.onLoad || componentTypes.some(d => this.route.options[d]?.preload));
723
+ };
724
+ __init = opts => {
725
+ // Validate the search params and stabilize them
726
+ this.parentMatch = opts.parentMatch;
727
+ const parentSearch = this.parentMatch?.state.search ?? this.router.state.latestLocation.search;
728
+ try {
729
+ const validator = typeof this.route.options.validateSearch === 'object' ? this.route.options.validateSearch.parse : this.route.options.validateSearch;
730
+ let nextSearch = validator?.(parentSearch) ?? {};
731
+ this.store.setState(s => ({
732
+ ...s,
733
+ routeSearch: nextSearch,
734
+ search: {
735
+ ...parentSearch,
736
+ ...nextSearch
737
+ }
738
+ }));
739
+ componentTypes.map(async type => {
740
+ const component = this.route.options[type];
741
+ if (typeof this[type] !== 'function') {
742
+ this[type] = component;
743
+ }
744
+ });
745
+ const parent = this.parentMatch;
746
+ this.routeContext = this.route.options.getContext?.({
747
+ parentContext: parent?.routeContext,
748
+ context: parent?.context,
749
+ params: this.params,
750
+ search: this.state.search
751
+ }) || {};
752
+ this.context = parent ? {
753
+ ...parent.context,
754
+ ...this.routeContext
755
+ } : {
756
+ ...this.router?.options.context,
757
+ ...this.routeContext
758
+ };
759
+ } catch (err) {
760
+ console.error(err);
761
+ const error = new Error('Invalid search params found', {
762
+ cause: err
763
+ });
764
+ error.code = 'INVALID_SEARCH_PARAMS';
765
+ this.store.setState(s => ({
766
+ ...s,
767
+ status: 'error',
768
+ error: error
769
+ }));
770
+
771
+ // Do not proceed with loading the route
772
+ return;
773
+ }
774
+ };
701
775
  cancel = () => {
702
776
  this.abortController?.abort();
703
777
  };
704
778
  load = async opts => {
705
779
  // If the match is invalid, errored or idle, trigger it to load
706
- if (this.store.state.status !== 'pending') {
780
+ if (this.state.status !== 'pending') {
707
781
  await this.fetch(opts);
708
782
  }
709
783
  };
@@ -720,7 +794,7 @@
720
794
  // If the match was in an error state, set it
721
795
  // to a loading state again. Otherwise, keep it
722
796
  // as loading or resolved
723
- if (this.store.state.status === 'idle') {
797
+ if (this.state.status === 'idle') {
724
798
  this.store.setState(s => ({
725
799
  ...s,
726
800
  status: 'pending'
@@ -742,9 +816,11 @@
742
816
  if (this.route.options.onLoad) {
743
817
  return this.route.options.onLoad({
744
818
  params: this.params,
745
- search: this.store.state.search,
819
+ search: this.state.search,
746
820
  signal: this.abortController.signal,
747
- preload: !!opts?.preload
821
+ preload: !!opts?.preload,
822
+ routeContext: this.routeContext,
823
+ context: this.context
748
824
  });
749
825
  }
750
826
  return;
@@ -772,50 +848,6 @@
772
848
  });
773
849
  return this.__loadPromise;
774
850
  };
775
- #hasLoaders = () => {
776
- return !!(this.route.options.onLoad || componentTypes.some(d => this.route.options[d]?.preload));
777
- };
778
- __setParentMatch = parentMatch => {
779
- if (!this.parentMatch && parentMatch) {
780
- this.parentMatch = parentMatch;
781
- }
782
- };
783
- __validate = () => {
784
- // Validate the search params and stabilize them
785
- const parentSearch = this.parentMatch?.store.state.search ?? this.router.store.state.latestLocation.search;
786
- try {
787
- const validator = typeof this.route.options.validateSearch === 'object' ? this.route.options.validateSearch.parse : this.route.options.validateSearch;
788
- let nextSearch = validator?.(parentSearch) ?? {};
789
- this.store.setState(s => ({
790
- ...s,
791
- routeSearch: nextSearch,
792
- search: {
793
- ...parentSearch,
794
- ...nextSearch
795
- }
796
- }));
797
- componentTypes.map(async type => {
798
- const component = this.route.options[type];
799
- if (typeof this[type] !== 'function') {
800
- this[type] = component;
801
- }
802
- });
803
- } catch (err) {
804
- console.error(err);
805
- const error = new Error('Invalid search params found', {
806
- cause: err
807
- });
808
- error.code = 'INVALID_SEARCH_PARAMS';
809
- this.store.setState(s => ({
810
- ...s,
811
- status: 'error',
812
- error: error
813
- }));
814
-
815
- // Do not proceed with loading the route
816
- return;
817
- }
818
- };
819
851
  }
820
852
 
821
853
  const defaultParseSearch = parseSearchWith(JSON.parse);
@@ -900,7 +932,12 @@
900
932
  parseSearch: options?.parseSearch ?? defaultParseSearch,
901
933
  fetchServerDataFn: options?.fetchServerDataFn ?? defaultFetchServerDataFn
902
934
  };
903
- this.store = new Store(getInitialRouterState());
935
+ this.store = new Store(getInitialRouterState(), {
936
+ onUpdate: state => {
937
+ this.state = state;
938
+ }
939
+ });
940
+ this.state = this.store.state;
904
941
  this.basepath = '';
905
942
  this.update(options);
906
943
 
@@ -914,7 +951,7 @@
914
951
  // Mount only does anything on the client
915
952
  if (!isServer) {
916
953
  // If the router matches are empty, load the matches
917
- if (!this.store.state.currentMatches.length) {
954
+ if (!this.state.currentMatches.length) {
918
955
  this.load();
919
956
  }
920
957
  const visibilityChangeEvent = 'visibilitychange';
@@ -953,7 +990,9 @@
953
990
  currentLocation: parsedLocation
954
991
  }));
955
992
  this.#unsubHistory = this.history.listen(() => {
956
- this.load(this.#parseLocation(this.store.state.latestLocation));
993
+ this.load({
994
+ next: this.#parseLocation(this.state.latestLocation)
995
+ });
957
996
  });
958
997
  }
959
998
  const {
@@ -979,11 +1018,11 @@
979
1018
  });
980
1019
  };
981
1020
  cancelMatches = () => {
982
- [...this.store.state.currentMatches, ...(this.store.state.pendingMatches || [])].forEach(match => {
1021
+ [...this.state.currentMatches, ...(this.state.pendingMatches || [])].forEach(match => {
983
1022
  match.cancel();
984
1023
  });
985
1024
  };
986
- load = async next => {
1025
+ load = async opts => {
987
1026
  let now = Date.now();
988
1027
  const startedAt = now;
989
1028
  this.startedLoadingAt = startedAt;
@@ -992,29 +1031,31 @@
992
1031
  this.cancelMatches();
993
1032
  let matches;
994
1033
  this.store.batch(() => {
995
- if (next) {
1034
+ if (opts?.next) {
996
1035
  // Ingest the new location
997
1036
  this.store.setState(s => ({
998
1037
  ...s,
999
- latestLocation: next
1038
+ latestLocation: opts.next
1000
1039
  }));
1001
1040
  }
1002
1041
 
1003
1042
  // Match the routes
1004
- matches = this.matchRoutes(this.store.state.latestLocation.pathname, {
1043
+ matches = this.matchRoutes(this.state.latestLocation.pathname, {
1005
1044
  strictParseParams: true
1006
1045
  });
1007
1046
  this.store.setState(s => ({
1008
1047
  ...s,
1009
1048
  status: 'pending',
1010
1049
  pendingMatches: matches,
1011
- pendingLocation: this.store.state.latestLocation
1050
+ pendingLocation: this.state.latestLocation
1012
1051
  }));
1013
1052
  });
1014
1053
 
1015
1054
  // Load the matches
1016
1055
  try {
1017
- await this.loadMatches(matches);
1056
+ await this.loadMatches(matches
1057
+ // opts
1058
+ );
1018
1059
  } catch (err) {
1019
1060
  console.warn(err);
1020
1061
  invariant(false, 'Matches failed to load due to error above ☝️. Navigation cancelled!');
@@ -1023,7 +1064,7 @@
1023
1064
  // Ignore side-effects of outdated side-effects
1024
1065
  return this.navigationPromise;
1025
1066
  }
1026
- const previousMatches = this.store.state.currentMatches;
1067
+ const previousMatches = this.state.currentMatches;
1027
1068
  const exiting = [],
1028
1069
  staying = [];
1029
1070
  previousMatches.forEach(d => {
@@ -1040,11 +1081,11 @@
1040
1081
  exiting.forEach(d => {
1041
1082
  d.__onExit?.({
1042
1083
  params: d.params,
1043
- search: d.store.state.routeSearch
1084
+ search: d.state.routeSearch
1044
1085
  });
1045
1086
 
1046
1087
  // Clear non-loading error states when match leaves
1047
- if (d.store.state.status === 'error') {
1088
+ if (d.state.status === 'error') {
1048
1089
  this.store.setState(s => ({
1049
1090
  ...s,
1050
1091
  status: 'idle',
@@ -1055,21 +1096,19 @@
1055
1096
  staying.forEach(d => {
1056
1097
  d.route.options.onTransition?.({
1057
1098
  params: d.params,
1058
- search: d.store.state.routeSearch
1099
+ search: d.state.routeSearch
1059
1100
  });
1060
1101
  });
1061
1102
  entering.forEach(d => {
1062
1103
  d.__onExit = d.route.options.onLoaded?.({
1063
1104
  params: d.params,
1064
- search: d.store.state.search
1105
+ search: d.state.search
1065
1106
  });
1066
- // delete this.store.state.matchCache[d.id] // TODO:
1067
1107
  });
1068
-
1069
1108
  this.store.setState(s => ({
1070
1109
  ...s,
1071
1110
  status: 'idle',
1072
- currentLocation: this.store.state.latestLocation,
1111
+ currentLocation: this.state.latestLocation,
1073
1112
  currentMatches: matches,
1074
1113
  pendingLocation: undefined,
1075
1114
  pendingMatches: undefined
@@ -1082,7 +1121,7 @@
1082
1121
  invariant(route, `Route with id "${id}" not found`);
1083
1122
  return route;
1084
1123
  };
1085
- loadRoute = async (navigateOpts = this.store.state.latestLocation) => {
1124
+ loadRoute = async (navigateOpts = this.state.latestLocation) => {
1086
1125
  const next = this.buildNext(navigateOpts);
1087
1126
  const matches = this.matchRoutes(next.pathname, {
1088
1127
  strictParseParams: true
@@ -1090,7 +1129,7 @@
1090
1129
  await this.loadMatches(matches);
1091
1130
  return matches;
1092
1131
  };
1093
- preloadRoute = async (navigateOpts = this.store.state.latestLocation) => {
1132
+ preloadRoute = async (navigateOpts = this.state.latestLocation) => {
1094
1133
  const next = this.buildNext(navigateOpts);
1095
1134
  const matches = this.matchRoutes(next.pathname, {
1096
1135
  strictParseParams: true
@@ -1105,7 +1144,7 @@
1105
1144
  if (!this.routeTree) {
1106
1145
  return matches;
1107
1146
  }
1108
- const existingMatches = [...this.store.state.currentMatches, ...(this.store.state.pendingMatches ?? [])];
1147
+ const existingMatches = [...this.state.currentMatches, ...(this.state.pendingMatches ?? [])];
1109
1148
  const findInRouteTree = async routes => {
1110
1149
  const parentMatch = last(matches);
1111
1150
  let params = parentMatch?.params ?? {};
@@ -1151,9 +1190,7 @@
1151
1190
  matchingRoutes.forEach(foundRoute => {
1152
1191
  const interpolatedPath = interpolatePath(foundRoute.path, params);
1153
1192
  const matchId = interpolatePath(foundRoute.id, params, true);
1154
- const match = existingMatches.find(d => d.id === matchId) ||
1155
- // this.store.state.matchCache[matchId]?.match || // TODO:
1156
- new RouteMatch(this, foundRoute, {
1193
+ const match = existingMatches.find(d => d.id === matchId) || new RouteMatch(this, foundRoute, {
1157
1194
  id: matchId,
1158
1195
  params,
1159
1196
  pathname: joinPaths([this.basepath, interpolatedPath])
@@ -1169,14 +1206,8 @@
1169
1206
  findInRouteTree([this.routeTree]);
1170
1207
  return matches;
1171
1208
  };
1172
- loadMatches = async (resolvedMatches, loaderOpts) => {
1173
- linkMatches(resolvedMatches);
1174
-
1175
- // this.cleanMatchCache()
1176
- resolvedMatches.forEach(async match => {
1177
- // Validate the match (loads search params etc)
1178
- match.__validate();
1179
- });
1209
+ loadMatches = async (resolvedMatches, opts) => {
1210
+ initMatches(resolvedMatches);
1180
1211
 
1181
1212
  // Check each match middleware to see if the route can be accessed
1182
1213
  await Promise.all(resolvedMatches.map(async match => {
@@ -1186,7 +1217,7 @@
1186
1217
  match
1187
1218
  });
1188
1219
  } catch (err) {
1189
- if (!loaderOpts?.preload) {
1220
+ if (!opts?.preload) {
1190
1221
  match.route.options.onLoadError?.(err);
1191
1222
  }
1192
1223
  throw err;
@@ -1194,14 +1225,16 @@
1194
1225
  }));
1195
1226
  const matchPromises = resolvedMatches.map(async (match, index) => {
1196
1227
  const prevMatch = resolvedMatches[1];
1197
- const search = match.store.state.search;
1198
- if (search.__data?.matchId && search.__data.matchId !== match.id) {
1199
- return;
1200
- }
1228
+ match.state.search;
1229
+
1230
+ // if (opts?.filter && !opts.filter(match)) {
1231
+ // return
1232
+ // }
1233
+
1201
1234
  match.load({
1202
- preload: loaderOpts?.preload
1235
+ preload: opts?.preload
1203
1236
  });
1204
- if (match.store.state.status !== 'success' && match.__loadPromise) {
1237
+ if (match.state.status !== 'success' && match.__loadPromise) {
1205
1238
  // Wait for the first sign of activity from the match
1206
1239
  await match.__loadPromise;
1207
1240
  }
@@ -1223,7 +1256,7 @@
1223
1256
  };
1224
1257
  navigate = async ({
1225
1258
  from,
1226
- to = '.',
1259
+ to = '',
1227
1260
  search,
1228
1261
  hash,
1229
1262
  replace,
@@ -1257,19 +1290,22 @@
1257
1290
  to: location.to ? this.resolvePath(location.from ?? '', location.to) : undefined
1258
1291
  };
1259
1292
  const next = this.buildNext(location);
1260
- if (opts?.pending) {
1261
- if (!this.store.state.pendingLocation) {
1262
- return false;
1263
- }
1264
- return matchPathname(this.basepath, this.store.state.pendingLocation.pathname, {
1265
- ...opts,
1266
- to: next.pathname
1267
- });
1293
+ const baseLocation = opts?.pending ? this.state.pendingLocation : this.state.currentLocation;
1294
+ if (!baseLocation) {
1295
+ return false;
1268
1296
  }
1269
- return matchPathname(this.basepath, this.store.state.currentLocation.pathname, {
1297
+ const match = matchPathname(this.basepath, baseLocation.pathname, {
1270
1298
  ...opts,
1271
1299
  to: next.pathname
1272
1300
  });
1301
+ if (!match) {
1302
+ return false;
1303
+ }
1304
+ if (opts?.includeSearch ?? true) {
1305
+ console.log(baseLocation.search, next.search, partialDeepEqual(baseLocation.search, next.search));
1306
+ return partialDeepEqual(baseLocation.search, next.search) ? match : false;
1307
+ }
1308
+ return match;
1273
1309
  };
1274
1310
  buildLink = ({
1275
1311
  from,
@@ -1312,17 +1348,16 @@
1312
1348
  const preloadDelay = userPreloadDelay ?? this.options.defaultPreloadDelay ?? 0;
1313
1349
 
1314
1350
  // Compare path/hash for matches
1315
- const pathIsEqual = this.store.state.currentLocation.pathname === next.pathname;
1316
- const currentPathSplit = this.store.state.currentLocation.pathname.split('/');
1351
+ const currentPathSplit = this.state.currentLocation.pathname.split('/');
1317
1352
  const nextPathSplit = next.pathname.split('/');
1318
1353
  const pathIsFuzzyEqual = nextPathSplit.every((d, i) => d === currentPathSplit[i]);
1319
- const hashIsEqual = this.store.state.currentLocation.hash === next.hash;
1320
1354
  // Combine the matches based on user options
1321
- const pathTest = activeOptions?.exact ? pathIsEqual : pathIsFuzzyEqual;
1322
- const hashTest = activeOptions?.includeHash ? hashIsEqual : true;
1355
+ const pathTest = activeOptions?.exact ? this.state.currentLocation.pathname === next.pathname : pathIsFuzzyEqual;
1356
+ const hashTest = activeOptions?.includeHash ? this.state.currentLocation.hash === next.hash : true;
1357
+ const searchTest = activeOptions?.includeSearch ?? true ? partialDeepEqual(this.state.currentLocation.search, next.search) : true;
1323
1358
 
1324
1359
  // The final "active" test
1325
- const isActive = pathTest && hashTest;
1360
+ const isActive = pathTest && hashTest && searchTest;
1326
1361
 
1327
1362
  // The click handler
1328
1363
  const handleClick = e => {
@@ -1379,21 +1414,18 @@
1379
1414
  dehydrate = () => {
1380
1415
  return {
1381
1416
  state: {
1382
- ...pick(this.store.state, ['latestLocation', 'currentLocation', 'status', 'lastUpdated']),
1383
- currentMatches: this.store.state.currentMatches.map(match => ({
1417
+ ...pick(this.state, ['latestLocation', 'currentLocation', 'status', 'lastUpdated']),
1418
+ currentMatches: this.state.currentMatches.map(match => ({
1384
1419
  id: match.id,
1385
1420
  state: {
1386
- ...pick(match.store.state, ['status'])
1421
+ status: match.state.status
1387
1422
  }
1388
1423
  }))
1389
- },
1390
- context: this.options.context
1424
+ }
1391
1425
  };
1392
1426
  };
1393
1427
  hydrate = dehydratedRouter => {
1394
1428
  this.store.setState(s => {
1395
- this.options.context = dehydratedRouter.context;
1396
-
1397
1429
  // Match the routes
1398
1430
  const currentMatches = this.matchRoutes(dehydratedRouter.state.latestLocation.pathname, {
1399
1431
  strictParseParams: true
@@ -1406,7 +1438,7 @@
1406
1438
  ...dehydratedMatch.state
1407
1439
  }));
1408
1440
  });
1409
- currentMatches.forEach(match => match.__validate());
1441
+ initMatches(currentMatches);
1410
1442
  return {
1411
1443
  ...s,
1412
1444
  ...dehydratedRouter.state,
@@ -1417,9 +1449,10 @@
1417
1449
  #buildRouteTree = routeTree => {
1418
1450
  const recurseRoutes = routes => {
1419
1451
  routes.forEach((route, i) => {
1420
- route.init();
1421
- route.originalIndex = i;
1422
- route.router = this;
1452
+ route.init({
1453
+ originalIndex: i,
1454
+ router: this
1455
+ });
1423
1456
  const existingRoute = this.routesById[route.id];
1424
1457
  if (existingRoute) {
1425
1458
  {
@@ -1457,9 +1490,10 @@
1457
1490
  this.load();
1458
1491
  };
1459
1492
  #buildLocation = (dest = {}) => {
1460
- const fromPathname = dest.fromCurrent ? this.store.state.latestLocation.pathname : dest.from ?? this.store.state.latestLocation.pathname;
1461
- let pathname = resolvePath(this.basepath ?? '/', fromPathname, `${dest.to ?? '.'}`);
1462
- const fromMatches = this.matchRoutes(this.store.state.latestLocation.pathname, {
1493
+ dest.fromCurrent = dest.fromCurrent ?? dest.to === '';
1494
+ const fromPathname = dest.fromCurrent ? this.state.latestLocation.pathname : dest.from ?? this.state.latestLocation.pathname;
1495
+ let pathname = resolvePath(this.basepath ?? '/', fromPathname, `${dest.to ?? ''}`);
1496
+ const fromMatches = this.matchRoutes(this.state.latestLocation.pathname, {
1463
1497
  strictParseParams: true
1464
1498
  });
1465
1499
  const toMatches = this.matchRoutes(pathname);
@@ -1475,7 +1509,7 @@
1475
1509
  pathname = interpolatePath(pathname, nextParams ?? {});
1476
1510
 
1477
1511
  // Pre filters first
1478
- const preFilteredSearch = dest.__preSearchFilters?.length ? dest.__preSearchFilters?.reduce((prev, next) => next(prev), this.store.state.latestLocation.search) : this.store.state.latestLocation.search;
1512
+ const preFilteredSearch = dest.__preSearchFilters?.length ? dest.__preSearchFilters?.reduce((prev, next) => next(prev), this.state.latestLocation.search) : this.state.latestLocation.search;
1479
1513
 
1480
1514
  // Then the link/navigate function
1481
1515
  const destSearch = dest.search === true ? preFilteredSearch // Preserve resolvedFrom true
@@ -1485,15 +1519,15 @@
1485
1519
 
1486
1520
  // Then post filters
1487
1521
  const postFilteredSearch = dest.__postSearchFilters?.length ? dest.__postSearchFilters.reduce((prev, next) => next(prev), destSearch) : destSearch;
1488
- const search = replaceEqualDeep(this.store.state.latestLocation.search, postFilteredSearch);
1522
+ const search = replaceEqualDeep(this.state.latestLocation.search, postFilteredSearch);
1489
1523
  const searchStr = this.options.stringifySearch(search);
1490
- let hash = dest.hash === true ? this.store.state.latestLocation.hash : functionalUpdate(dest.hash, this.store.state.latestLocation.hash);
1524
+ let hash = dest.hash === true ? this.state.latestLocation.hash : functionalUpdate(dest.hash, this.state.latestLocation.hash);
1491
1525
  hash = hash ? `#${hash}` : '';
1492
1526
  return {
1493
1527
  pathname,
1494
1528
  search,
1495
1529
  searchStr,
1496
- state: this.store.state.latestLocation.state,
1530
+ state: this.state.latestLocation.state,
1497
1531
  hash,
1498
1532
  href: `${pathname}${searchStr}${hash}`,
1499
1533
  key: dest.key
@@ -1507,7 +1541,7 @@
1507
1541
  if (!location.replace) {
1508
1542
  nextAction = 'push';
1509
1543
  }
1510
- const isSameUrl = this.store.state.latestLocation.href === next.href;
1544
+ const isSameUrl = this.state.latestLocation.href === next.href;
1511
1545
  if (isSameUrl && !next.key) {
1512
1546
  nextAction = 'replace';
1513
1547
  }
@@ -1517,7 +1551,7 @@
1517
1551
  ...next.state
1518
1552
  });
1519
1553
 
1520
- // this.load(this.#parseLocation(this.store.state.latestLocation))
1554
+ // this.load(this.#parseLocation(this.state.latestLocation))
1521
1555
 
1522
1556
  return this.navigationPromise = new Promise(resolve => {
1523
1557
  const previousNavigationResolve = this.resolveNavigation;
@@ -1543,12 +1577,12 @@
1543
1577
  function isCtrlEvent(e) {
1544
1578
  return !!(e.metaKey || e.altKey || e.ctrlKey || e.shiftKey);
1545
1579
  }
1546
- function linkMatches(matches) {
1580
+ function initMatches(matches) {
1547
1581
  matches.forEach((match, index) => {
1548
- const parent = matches[index - 1];
1549
- if (parent) {
1550
- match.__setParentMatch(parent);
1551
- }
1582
+ const parentMatch = matches[index - 1];
1583
+ match.__init({
1584
+ parentMatch
1585
+ });
1552
1586
  });
1553
1587
  }
1554
1588
 
@@ -1624,7 +1658,7 @@
1624
1658
  //
1625
1659
 
1626
1660
  function useLinkProps(options) {
1627
- const router = useRouter();
1661
+ const router = useRouterContext();
1628
1662
  const {
1629
1663
  // custom props
1630
1664
  type,
@@ -1749,7 +1783,7 @@
1749
1783
  ...rest
1750
1784
  }) {
1751
1785
  router.update(rest);
1752
- const currentMatches = useStore(router.store, s => s.currentMatches, undefined);
1786
+ const currentMatches = useStore(router.store, s => s.currentMatches);
1753
1787
  React__namespace.useEffect(router.mount, [router]);
1754
1788
  return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement(routerContext.Provider, {
1755
1789
  value: {
@@ -1759,22 +1793,24 @@
1759
1793
  value: [undefined, ...currentMatches]
1760
1794
  }, /*#__PURE__*/React__namespace.createElement(Outlet, null))));
1761
1795
  }
1762
- function useRouter() {
1796
+ function useRouterContext() {
1763
1797
  const value = React__namespace.useContext(routerContext);
1764
1798
  warning(!value, 'useRouter must be used inside a <Router> component!');
1799
+ useStore(value.router.store);
1765
1800
  return value.router;
1766
1801
  }
1767
- function useRouterStore(selector, shallow) {
1768
- const router = useRouter();
1769
- return useStore(router.store, selector, shallow);
1802
+ function useRouter(track, shallow) {
1803
+ const router = useRouterContext();
1804
+ useStore(router.store, track, shallow);
1805
+ return router;
1770
1806
  }
1771
1807
  function useMatches() {
1772
1808
  return React__namespace.useContext(matchesContext);
1773
1809
  }
1774
1810
  function useMatch(opts) {
1775
- const router = useRouter();
1811
+ const router = useRouterContext();
1776
1812
  const nearestMatch = useMatches()[0];
1777
- const match = opts?.from ? router.store.state.currentMatches.find(d => d.route.id === opts?.from) : nearestMatch;
1813
+ const match = opts?.from ? router.state.currentMatches.find(d => d.route.id === opts?.from) : nearestMatch;
1778
1814
  invariant(match, `Could not find ${opts?.from ? `an active match from "${opts.from}"` : 'a nearest match!'}`);
1779
1815
  if (opts?.strict ?? true) {
1780
1816
  invariant(nearestMatch.route.id == match?.route.id, `useMatch("${match?.route.id}") is being called in a component that is meant to render the '${nearestMatch.route.id}' route. Did you mean to 'useMatch("${match?.route.id}", { strict: false })' or 'useRoute("${match?.route.id}")' instead?`);
@@ -1783,26 +1819,26 @@
1783
1819
  return match;
1784
1820
  }
1785
1821
  function useRoute(routeId) {
1786
- const router = useRouter();
1822
+ const router = useRouterContext();
1787
1823
  const resolvedRoute = router.getRoute(routeId);
1788
1824
  invariant(resolvedRoute, `Could not find a route for route "${routeId}"! Did you forget to add it to your route config?`);
1789
1825
  return resolvedRoute;
1790
1826
  }
1791
1827
  function useSearch(opts) {
1792
1828
  const match = useMatch(opts);
1793
- useStore(match.store, d => opts?.track?.(d.search) ?? d.search);
1794
- return match.store.state.search;
1829
+ useStore(match.store, d => opts?.track?.(d.search) ?? d.search, true);
1830
+ return match.state.search;
1795
1831
  }
1796
1832
  function useParams(opts) {
1797
- const router = useRouter();
1833
+ const router = useRouterContext();
1798
1834
  useStore(router.store, d => {
1799
1835
  const params = last(d.currentMatches)?.params;
1800
1836
  return opts?.track?.(params) ?? params;
1801
- });
1802
- return last(router.store.state.currentMatches)?.params;
1837
+ }, true);
1838
+ return last(router.state.currentMatches)?.params;
1803
1839
  }
1804
1840
  function useNavigate(defaultOpts) {
1805
- const router = useRouter();
1841
+ const router = useRouterContext();
1806
1842
  return opts => {
1807
1843
  return router.navigate({
1808
1844
  ...defaultOpts,
@@ -1811,7 +1847,7 @@
1811
1847
  };
1812
1848
  }
1813
1849
  function useMatchRoute() {
1814
- const router = useRouter();
1850
+ const router = useRouterContext();
1815
1851
  return opts => {
1816
1852
  const {
1817
1853
  pending,
@@ -1850,17 +1886,17 @@
1850
1886
  matches,
1851
1887
  match
1852
1888
  }) {
1853
- const router = useRouter();
1854
- useStore(match.store);
1889
+ const router = useRouterContext();
1890
+ useStore(match.store, store => [store.status, store.error], true);
1855
1891
  const defaultPending = React__namespace.useCallback(() => null, []);
1856
1892
  const Inner = React__namespace.useCallback(props => {
1857
- if (props.match.store.state.status === 'error') {
1858
- throw props.match.store.state.error;
1893
+ if (props.match.state.status === 'error') {
1894
+ throw props.match.state.error;
1859
1895
  }
1860
- if (props.match.store.state.status === 'success') {
1896
+ if (props.match.state.status === 'success') {
1861
1897
  return /*#__PURE__*/React__namespace.createElement(props.match.component ?? router.options.defaultComponent ?? Outlet);
1862
1898
  }
1863
- if (props.match.store.state.status === 'pending') {
1899
+ if (props.match.state.status === 'pending') {
1864
1900
  throw props.match.__loadPromise;
1865
1901
  }
1866
1902
  invariant(false, 'Idle routeMatch status encountered during rendering! You should never see this. File an issue!');
@@ -1869,7 +1905,7 @@
1869
1905
  const errorComponent = match.errorComponent ?? router.options.defaultErrorComponent;
1870
1906
  return /*#__PURE__*/React__namespace.createElement(matchesContext.Provider, {
1871
1907
  value: matches
1872
- }, /*#__PURE__*/React__namespace.createElement(React__namespace.Suspense, {
1908
+ }, match.route.options.wrapInSuspense ?? true ? /*#__PURE__*/React__namespace.createElement(React__namespace.Suspense, {
1873
1909
  fallback: /*#__PURE__*/React__namespace.createElement(PendingComponent, null)
1874
1910
  }, /*#__PURE__*/React__namespace.createElement(CatchBoundary, {
1875
1911
  key: match.route.id,
@@ -1877,7 +1913,13 @@
1877
1913
  match: match
1878
1914
  }, /*#__PURE__*/React__namespace.createElement(Inner, {
1879
1915
  match: match
1880
- }))));
1916
+ }))) : /*#__PURE__*/React__namespace.createElement(CatchBoundary, {
1917
+ key: match.route.id,
1918
+ errorComponent: errorComponent,
1919
+ match: match
1920
+ }, /*#__PURE__*/React__namespace.createElement(Inner, {
1921
+ match: match
1922
+ })));
1881
1923
  }
1882
1924
 
1883
1925
  // This is the messiest thing ever... I'm either seriously tired (likely) or
@@ -1906,17 +1948,17 @@
1906
1948
  }
1907
1949
  function CatchBoundaryInner(props) {
1908
1950
  const [activeErrorState, setActiveErrorState] = React__namespace.useState(props.errorState);
1909
- const router = useRouter();
1951
+ const router = useRouterContext();
1910
1952
  const errorComponent = props.errorComponent ?? DefaultErrorBoundary;
1911
1953
  const prevKeyRef = React__namespace.useRef('');
1912
1954
  React__namespace.useEffect(() => {
1913
1955
  if (activeErrorState) {
1914
- if (router.store.state.currentLocation.key !== prevKeyRef.current) {
1956
+ if (router.state.currentLocation.key !== prevKeyRef.current) {
1915
1957
  setActiveErrorState({});
1916
1958
  }
1917
1959
  }
1918
- prevKeyRef.current = router.store.state.currentLocation.key;
1919
- }, [activeErrorState, router.store.state.currentLocation.key]);
1960
+ prevKeyRef.current = router.state.currentLocation.key;
1961
+ }, [activeErrorState, router.state.currentLocation.key]);
1920
1962
  React__namespace.useEffect(() => {
1921
1963
  if (props.errorState.error) {
1922
1964
  setActiveErrorState(props.errorState);
@@ -2013,6 +2055,7 @@
2013
2055
  exports.matchesContext = matchesContext;
2014
2056
  exports.parsePathname = parsePathname;
2015
2057
  exports.parseSearchWith = parseSearchWith;
2058
+ exports.partialDeepEqual = partialDeepEqual;
2016
2059
  exports.pick = pick;
2017
2060
  exports.replaceEqualDeep = replaceEqualDeep;
2018
2061
  exports.resolvePath = resolvePath;
@@ -2030,7 +2073,7 @@
2030
2073
  exports.useParams = useParams;
2031
2074
  exports.useRoute = useRoute;
2032
2075
  exports.useRouter = useRouter;
2033
- exports.useRouterStore = useRouterStore;
2076
+ exports.useRouterContext = useRouterContext;
2034
2077
  exports.useSearch = useSearch;
2035
2078
  exports.useStore = useStore;
2036
2079
  exports.warning = warning;