@react-navigation/core 7.0.0-alpha.10 → 7.0.0-alpha.12

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.
@@ -287,29 +287,71 @@ const matchAgainstConfigs = (remaining: string, configs: RouteConfig[]) => {
287
287
 
288
288
  // If our regex matches, we need to extract params from the path
289
289
  if (match) {
290
- const matchedParams = config.pattern
291
- ?.split('/')
292
- .filter((p) => p.startsWith(':'))
293
- .reduce<Record<string, any>>(
294
- (acc, p, i) =>
295
- Object.assign(acc, {
296
- // The param segments appear every second item starting from 2 in the regex match result
297
- [p]: match![(i + 1) * 2].replace(/\//, ''),
290
+ const matchResult = config.pattern?.split('/').reduce<{
291
+ pos: number; // Position of the current path param segment in the path (e.g in pattern `a/:b/:c`, `:a` is 0 and `:b` is 1)
292
+ matchedParams: Record<string, Record<string, string>>; // The extracted params
293
+ }>(
294
+ (acc, p, index) => {
295
+ if (!p.startsWith(':')) {
296
+ return acc;
297
+ }
298
+
299
+ // Path parameter so increment position for the segment
300
+ acc.pos += 1;
301
+
302
+ const decodedParamSegment = decodeURIComponent(
303
+ // The param segments appear every second item starting from 2 in the regex match result
304
+ match![(acc.pos + 1) * 2]
305
+ );
306
+
307
+ Object.assign(acc.matchedParams, {
308
+ [p]: Object.assign(acc.matchedParams[p] || {}, {
309
+ [index]: decodedParamSegment.replace(/\//, ''),
298
310
  }),
299
- {}
300
- );
311
+ });
312
+
313
+ return acc;
314
+ },
315
+ { pos: -1, matchedParams: {} }
316
+ );
317
+
318
+ const matchedParams = matchResult.matchedParams || {};
301
319
 
302
320
  routes = config.routeNames.map((name) => {
303
- const config = configs.find((c) => c.screen === name);
304
- const params = config?.path
321
+ const routeConfig = configs.find((c) => {
322
+ // Check matching name AND pattern in case same screen is used at different levels in config
323
+ return c.screen === name && config.pattern.startsWith(c.pattern);
324
+ });
325
+
326
+ // Normalize pattern to remove any leading, trailing slashes, duplicate slashes etc.
327
+ const normalizedPath = routeConfig?.path
328
+ .split('/')
329
+ .filter(Boolean)
330
+ .join('/');
331
+
332
+ // Get the number of segments in the initial pattern
333
+ const numInitialSegments = routeConfig?.pattern
334
+ // Extract the prefix from the pattern by removing the ending path pattern (e.g pattern=`a/b/c/d` and normalizedPath=`c/d` becomes `a/b`)
335
+ .replace(new RegExp(`${escape(normalizedPath!)}$`), '')
336
+ ?.split('/').length;
337
+
338
+ const params = normalizedPath
305
339
  ?.split('/')
306
- .filter((p) => p.startsWith(':'))
307
- .reduce<Record<string, any>>((acc, p) => {
308
- const value = matchedParams[p];
340
+ .reduce<Record<string, unknown>>((acc, p, index) => {
341
+ if (!p.startsWith(':')) {
342
+ return acc;
343
+ }
344
+
345
+ // Get the real index of the path parameter in the matched path
346
+ // by offsetting by the number of segments in the initial pattern
347
+ const offset = numInitialSegments ? numInitialSegments - 1 : 0;
348
+ const value = matchedParams[p]?.[index + offset];
309
349
 
310
350
  if (value) {
311
351
  const key = p.replace(/^:/, '').replace(/\?$/, '');
312
- acc[key] = config.parse?.[key] ? config.parse[key](value) : value;
352
+ acc[key] = routeConfig?.parse?.[key]
353
+ ? routeConfig.parse[key](value)
354
+ : value;
313
355
  }
314
356
 
315
357
  return acc;
package/src/types.tsx CHANGED
@@ -555,7 +555,7 @@ export type ScreenListeners<
555
555
  EventMap extends EventMapBase,
556
556
  > = Partial<{
557
557
  [EventName in keyof (EventMap & EventMapCore<State>)]: EventListenerCallback<
558
- EventMap,
558
+ EventMap & EventMapCore<State>,
559
559
  EventName
560
560
  >;
561
561
  }>;
@@ -556,6 +556,10 @@ export function useNavigationBuilder<
556
556
  state = nextState;
557
557
 
558
558
  React.useEffect(() => {
559
+ // In strict mode, React will double-invoke effects.
560
+ // So we need to reset the flag if component was not unmounted
561
+ stateCleanedUp.current = false;
562
+
559
563
  setKey(navigatorKey);
560
564
 
561
565
  if (!getIsInitial()) {