Skip to content

Commit 418ac75

Browse files
committed
chore(react-router): cleaning up util files
1 parent 71e55ad commit 418ac75

File tree

9 files changed

+132
-141
lines changed

9 files changed

+132
-141
lines changed

packages/react-router/src/ReactRouter/ReactRouterViewStack.tsx

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,11 @@ import React from 'react';
1111
import type { PathMatch } from 'react-router';
1212
import { Navigate, UNSAFE_RouteContext as RouteContext } from 'react-router-dom';
1313

14-
import { analyzeRouteChildren, computeParentPath, extractRouteChildren } from './utils/computeParentPath';
15-
import { derivePathnameToMatch } from './utils/derivePathnameToMatch';
16-
import { matchPath } from './utils/matchPath';
17-
import { normalizePathnameForComparison } from './utils/normalizePath';
18-
import { isNavigateElement, sortViewsBySpecificity } from './utils/routeUtils';
14+
import { analyzeRouteChildren, computeParentPath } from './utils/computeParentPath';
15+
import { derivePathnameToMatch, matchPath } from './utils/pathMatching';
16+
import { normalizePathnameForComparison } from './utils/pathNormalization';
17+
import { extractRouteChildren, isNavigateElement } from './utils/routeElements';
18+
import { sortViewsBySpecificity } from './utils/viewItemUtils';
1919

2020
/**
2121
* Delay in milliseconds before removing a Navigate view item after a redirect.
@@ -51,7 +51,6 @@ const createDefaultMatch = (
5151
};
5252
};
5353

54-
5554
const computeRelativeToParent = (pathname: string, parentPath?: string): string | null => {
5655
if (!parentPath) return null;
5756
const normalizedParent = normalizePathnameForComparison(parentPath);
@@ -238,7 +237,6 @@ export class ReactRouterViewStack extends ViewStacks {
238237
// Flag to indicate this view should not be reused for this different parameterized path
239238
const shouldSkipForDifferentParam = isParameterRoute && match && previousMatch && !isSamePath;
240239

241-
242240
// Don't deactivate views automatically - let the StackManager handle view lifecycle
243241
// This preserves views in the stack for navigation history like native apps
244242
// Views will be hidden/shown by the StackManager's transition logic instead of being unmounted
@@ -519,8 +517,7 @@ export class ReactRouterViewStack extends ViewStacks {
519517

520518
// Check if current pathname is within this view's route hierarchy
521519
const isWithinRouteHierarchy =
522-
normalizedCurrentPath === normalizedViewPath ||
523-
normalizedCurrentPath.startsWith(normalizedViewPath + '/');
520+
normalizedCurrentPath === normalizedViewPath || normalizedCurrentPath.startsWith(normalizedViewPath + '/');
524521

525522
if (!isWithinRouteHierarchy) {
526523
// View is outside current route hierarchy, remove it

packages/react-router/src/ReactRouter/StackManager.tsx

Lines changed: 12 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,10 @@ import React from 'react';
1010
import { Route } from 'react-router-dom';
1111

1212
import { clonePageElement } from './clonePageElement';
13-
import {
14-
analyzeRouteChildren,
15-
computeCommonPrefix,
16-
computeParentPath,
17-
extractRouteChildren,
18-
} from './utils/computeParentPath';
19-
import { derivePathnameToMatch } from './utils/derivePathnameToMatch';
20-
import { getRoutesChildren } from './utils/getRoutesChildren';
21-
import { matchPath } from './utils/matchPath';
22-
import { stripTrailingSlash } from './utils/normalizePath';
23-
import { isNavigateElement } from './utils/routeUtils';
13+
import { analyzeRouteChildren, computeCommonPrefix, computeParentPath } from './utils/computeParentPath';
14+
import { derivePathnameToMatch, matchPath } from './utils/pathMatching';
15+
import { stripTrailingSlash } from './utils/pathNormalization';
16+
import { extractRouteChildren, getRoutesChildren, isNavigateElement } from './utils/routeElements';
2417

2518
/**
2619
* Delay in milliseconds before unmounting a view after a transition completes.
@@ -149,7 +142,7 @@ export class StackManager extends React.PureComponent<StackManagerProps> {
149142
enteringViewItem: ViewItem | undefined;
150143
leavingViewItem: ViewItem | undefined;
151144
} {
152-
let enteringViewItem = this.context.findViewItemByRouteInfo(routeInfo, this.id);
145+
const enteringViewItem = this.context.findViewItemByRouteInfo(routeInfo, this.id);
153146
let leavingViewItem = this.context.findLeavingViewItemByRouteInfo(routeInfo, this.id);
154147

155148
// If we don't have a leaving view item, but the route info indicates
@@ -257,7 +250,8 @@ export class StackManager extends React.PureComponent<StackManagerProps> {
257250
return false;
258251
}
259252

260-
const routesChildren = getRoutesChildren(this.ionRouterOutlet.props.children) ?? this.ionRouterOutlet.props.children;
253+
const routesChildren =
254+
getRoutesChildren(this.ionRouterOutlet.props.children) ?? this.ionRouterOutlet.props.children;
261255
const routeChildren = React.Children.toArray(routesChildren).filter(
262256
(child): child is React.ReactElement => React.isValidElement(child) && child.type === Route
263257
);
@@ -382,11 +376,7 @@ export class StackManager extends React.PureComponent<StackManagerProps> {
382376
/**
383377
* Handles the delayed unmount of the leaving view item after a replace action.
384378
*/
385-
private handleLeavingViewUnmount(
386-
routeInfo: RouteInfo,
387-
enteringViewItem: ViewItem,
388-
leavingViewItem: ViewItem
389-
): void {
379+
private handleLeavingViewUnmount(routeInfo: RouteInfo, enteringViewItem: ViewItem, leavingViewItem: ViewItem): void {
390380
if (routeInfo.routeAction !== 'replace' || !leavingViewItem.ionPageElement) {
391381
return;
392382
}
@@ -538,9 +528,7 @@ export class StackManager extends React.PureComponent<StackManagerProps> {
538528
// the nested outlet's componentDidUpdate won't be called, so we must hide
539529
// the ion-page elements here to prevent them from remaining visible on top
540530
// of other content after navigation to a different route.
541-
const allViewsInOutlet = this.context.getViewItemsForOutlet
542-
? this.context.getViewItemsForOutlet(this.id)
543-
: [];
531+
const allViewsInOutlet = this.context.getViewItemsForOutlet ? this.context.getViewItemsForOutlet(this.id) : [];
544532
allViewsInOutlet.forEach((viewItem) => {
545533
hideIonPageElement(viewItem.ionPageElement);
546534
});
@@ -570,7 +558,9 @@ export class StackManager extends React.PureComponent<StackManagerProps> {
570558
}
571559

572560
// Find entering and leaving view items
573-
let { enteringViewItem, leavingViewItem } = this.findViewItems(routeInfo);
561+
const viewItems = this.findViewItems(routeInfo);
562+
let enteringViewItem = viewItems.enteringViewItem;
563+
const leavingViewItem = viewItems.leavingViewItem;
574564
const shouldUnmountLeavingViewItem = this.shouldUnmountLeavingView(routeInfo, enteringViewItem, leavingViewItem);
575565

576566
// Get parent path for nested outlets

packages/react-router/src/ReactRouter/utils/computeParentPath.ts

Lines changed: 2 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
1-
import React from 'react';
2-
import { Route } from 'react-router-dom';
1+
import type React from 'react';
32

4-
import { getRoutesChildren } from './getRoutesChildren';
5-
import { matchPath } from './matchPath';
3+
import { matchPath } from './pathMatching';
64

75
/**
86
* Finds the longest common prefix among an array of paths.
@@ -76,19 +74,6 @@ export interface ParentPathResult {
7674
outletMountPath: string | undefined;
7775
}
7876

79-
/**
80-
* Extracts Route children from a node (either directly or from a Routes wrapper).
81-
*
82-
* @param children The children to extract routes from.
83-
* @returns An array of Route elements.
84-
*/
85-
export const extractRouteChildren = (children: React.ReactNode): React.ReactElement[] => {
86-
const routesChildren = getRoutesChildren(children) ?? children;
87-
return React.Children.toArray(routesChildren).filter(
88-
(child): child is React.ReactElement => React.isValidElement(child) && child.type === Route
89-
);
90-
};
91-
9277
interface RouteAnalysis {
9378
hasRelativeRoutes: boolean;
9479
hasIndexRoute: boolean;

packages/react-router/src/ReactRouter/utils/derivePathnameToMatch.ts

Lines changed: 0 additions & 57 deletions
This file was deleted.

packages/react-router/src/ReactRouter/utils/getRoutesChildren.ts

Lines changed: 0 additions & 19 deletions
This file was deleted.

packages/react-router/src/ReactRouter/utils/matchPath.ts renamed to packages/react-router/src/ReactRouter/utils/pathMatching.ts

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import type { PathMatch } from 'react-router';
22
import { matchPath as reactRouterMatchPath } from 'react-router-dom';
33

4+
/**
5+
* Options for the matchPath function.
6+
*/
47
interface MatchPathOptions {
58
/**
69
* The pathname to match against.
@@ -99,3 +102,61 @@ export const matchPath = ({ pathname, componentProps }: MatchPathOptions): PathM
99102

100103
return reactRouterMatchPath(matchOptions, pathname);
101104
};
105+
106+
/**
107+
* Determines the portion of a pathname that a given route pattern should match against.
108+
* For absolute route patterns we return the full pathname. For relative patterns we
109+
* strip off the already-matched parent segments so React Router receives the remainder.
110+
*/
111+
export const derivePathnameToMatch = (fullPathname: string, routePath?: string): string => {
112+
if (!routePath || routePath === '' || routePath.startsWith('/')) {
113+
return fullPathname;
114+
}
115+
116+
const trimmedPath = fullPathname.startsWith('/') ? fullPathname.slice(1) : fullPathname;
117+
if (!trimmedPath) {
118+
return '';
119+
}
120+
121+
const fullSegments = trimmedPath.split('/').filter(Boolean);
122+
if (fullSegments.length === 0) {
123+
return '';
124+
}
125+
126+
const routeSegments = routePath.split('/').filter(Boolean);
127+
if (routeSegments.length === 0) {
128+
return trimmedPath;
129+
}
130+
131+
const wildcardIndex = routeSegments.findIndex((segment) => segment === '*' || segment === '**');
132+
133+
if (wildcardIndex >= 0) {
134+
const baseSegments = routeSegments.slice(0, wildcardIndex);
135+
if (baseSegments.length === 0) {
136+
return trimmedPath;
137+
}
138+
139+
const startIndex = fullSegments.findIndex((_, idx) =>
140+
baseSegments.every((seg, segIdx) => {
141+
const target = fullSegments[idx + segIdx];
142+
if (!target) {
143+
return false;
144+
}
145+
if (seg.startsWith(':')) {
146+
return true;
147+
}
148+
return target === seg;
149+
})
150+
);
151+
152+
if (startIndex >= 0) {
153+
return fullSegments.slice(startIndex).join('/');
154+
}
155+
}
156+
157+
if (routeSegments.length <= fullSegments.length) {
158+
return fullSegments.slice(fullSegments.length - routeSegments.length).join('/');
159+
}
160+
161+
return fullSegments[fullSegments.length - 1] ?? trimmedPath;
162+
};
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import React from 'react';
2+
import { Navigate, Route, Routes } from 'react-router-dom';
3+
4+
/**
5+
* Extracts the children from a Routes wrapper component.
6+
* The use of `<Routes />` is encouraged with React Router v6.
7+
*
8+
* @param node The React node to extract Routes children from.
9+
* @returns The children of the Routes component, or undefined if not found.
10+
*/
11+
export const getRoutesChildren = (node: React.ReactNode): React.ReactNode | undefined => {
12+
let routesNode: React.ReactNode;
13+
React.Children.forEach(node as React.ReactElement, (child: React.ReactElement) => {
14+
if (child.type === Routes) {
15+
routesNode = child;
16+
}
17+
});
18+
19+
if (routesNode) {
20+
// The children of the `<Routes />` component are most likely
21+
// (and should be) the `<Route />` components.
22+
return (routesNode as React.ReactElement).props.children;
23+
}
24+
return undefined;
25+
};
26+
27+
/**
28+
* Extracts Route children from a node (either directly or from a Routes wrapper).
29+
*
30+
* @param children The children to extract routes from.
31+
* @returns An array of Route elements.
32+
*/
33+
export const extractRouteChildren = (children: React.ReactNode): React.ReactElement[] => {
34+
const routesChildren = getRoutesChildren(children) ?? children;
35+
return React.Children.toArray(routesChildren).filter(
36+
(child): child is React.ReactElement => React.isValidElement(child) && child.type === Route
37+
);
38+
};
39+
40+
/**
41+
* Checks if a React element is a Navigate component (redirect).
42+
*
43+
* @param element The element to check.
44+
* @returns True if the element is a Navigate component.
45+
*/
46+
export const isNavigateElement = (element: unknown): boolean => {
47+
return (
48+
React.isValidElement(element) &&
49+
(element.type === Navigate || (typeof element.type === 'function' && element.type.name === 'Navigate'))
50+
);
51+
};

packages/react-router/src/ReactRouter/utils/routeUtils.ts renamed to packages/react-router/src/ReactRouter/utils/viewItemUtils.ts

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,5 @@
1-
import React from 'react';
2-
import { Navigate } from 'react-router-dom';
3-
41
import type { ViewItem } from '@ionic/react';
52

6-
/**
7-
* Checks if a React element is a Navigate component (redirect).
8-
*
9-
* @param element The element to check.
10-
* @returns True if the element is a Navigate component.
11-
*/
12-
export const isNavigateElement = (element: unknown): boolean => {
13-
return (
14-
React.isValidElement(element) &&
15-
(element.type === Navigate ||
16-
(typeof element.type === 'function' && element.type.name === 'Navigate'))
17-
);
18-
};
19-
203
/**
214
* Sorts view items by route specificity (most specific first).
225
* - Exact matches (no wildcards/params) come first

0 commit comments

Comments
 (0)