Compare commits

...

1 Commits

Author SHA1 Message Date
Sean Perkins
5f56f069e5 refactor(react-router): migrate stack manager to hooks 2023-08-01 16:03:17 -04:00

View File

@@ -1,6 +1,7 @@
import type { RouteInfo, StackContextState, ViewItem } from '@ionic/react'; import type { RouteInfo, StackContextState, ViewItem } from '@ionic/react';
import { RouteManagerContext, StackContext, generateId, getConfig } from '@ionic/react'; import { RouteManagerContext, StackContext, generateId, getConfig } from '@ionic/react';
import React from 'react'; import type { PropsWithChildren } from 'react';
import React, { cloneElement, useContext, useEffect, useMemo, useReducer, useRef, useState } from 'react';
import { matchPath } from 'react-router-dom'; import { matchPath } from 'react-router-dom';
import { clonePageElement } from './clonePageElement'; import { clonePageElement } from './clonePageElement';
@@ -11,76 +12,93 @@ interface StackManagerProps {
routeInfo: RouteInfo; routeInfo: RouteInfo;
} }
// eslint-disable-next-line @typescript-eslint/no-empty-interface
interface StackManagerState {}
const isViewVisible = (el: HTMLElement) => const isViewVisible = (el: HTMLElement) =>
!el.classList.contains('ion-page-invisible') && !el.classList.contains('ion-page-hidden'); !el.classList.contains('ion-page-invisible') && !el.classList.contains('ion-page-hidden');
export class StackManager extends React.PureComponent<StackManagerProps, StackManagerState> { export const StackManager = ({ children, ...props }: PropsWithChildren<StackManagerProps>) => {
id: string; const { routeInfo } = props;
context!: React.ContextType<typeof RouteManagerContext>; const {
ionRouterOutlet?: React.ReactElement; findViewItemByRouteInfo,
routerOutletElement: HTMLIonRouterOutletElement | undefined; findLeavingViewItemByRouteInfo,
prevProps?: StackManagerProps; findViewItemByPathname,
skipTransition: boolean; createViewItem,
addViewItem,
goBack,
getChildrenToRender,
clearOutlet,
} = useContext(RouteManagerContext);
stackContextValue: StackContextState = { const routerOutletRef = useRef<HTMLIonRouterOutletElement>();
registerIonPage: this.registerIonPage.bind(this), const ionRouterOutletRef = useRef<React.ReactElement>();
isInOutlet: () => true, const skipTransitionRef = useRef(false);
}; const clearOutletTimeout = useRef(null);
const pendingPageTransitionRef = useRef(false);
const prevProps = useRef<{ routeInfo: RouteInfo }>();
private clearOutletTimeout: any; const forceUpdate = useReducer((x) => x + 1, 0)[1];
private pendingPageTransition = false;
constructor(props: StackManagerProps) { const [id] = useState(generateId('routerOutlet'));
super(props);
this.registerIonPage = this.registerIonPage.bind(this);
this.transitionPage = this.transitionPage.bind(this);
this.handlePageTransition = this.handlePageTransition.bind(this);
this.id = generateId('routerOutlet');
this.prevProps = undefined;
this.skipTransition = false;
}
componentDidMount() { const stackContextValue: StackContextState = useMemo(
if (this.clearOutletTimeout) { () => ({
/** isInOutlet: () => true,
* The clearOutlet integration with React Router is a bit hacky. registerIonPage: (page: HTMLElement, routeInfo: RouteInfo) => {
* It uses a timeout to clear the outlet after a transition. const foundView = findViewItemByRouteInfo(routeInfo, id);
* In React v18, components are mounted and unmounted in development mode if (foundView) {
* to check for side effects. const oldPageElement = foundView.ionPageElement;
* foundView.ionPageElement = page;
* This clearTimeout prevents the outlet from being cleared when the component is re-mounted, foundView.ionRoute = true;
* which should only happen in development mode and as a result of a hot reload.
*/ /**
clearTimeout(this.clearOutletTimeout); * React 18 will unmount and remount IonPage
* elements in development mode when using createRoot.
* This can cause duplicate page transitions to occur.
*/
if (oldPageElement === page) {
return;
}
}
handlePageTransition(routeInfo);
},
}),
[routerOutletRef.current]
);
useEffect(() => {
const routerOutletElement = routerOutletRef.current;
if (routerOutletElement) {
// Mount behavior for the initial route
setupRouterOutlet(routerOutletElement);
handlePageTransition(routeInfo);
} }
if (this.routerOutletElement) { }, []);
this.setupRouterOutlet(this.routerOutletElement);
this.handlePageTransition(this.props.routeInfo); useEffect(() => {
const { pathname } = routeInfo;
if (pathname !== prevProps.current?.routeInfo.pathname) {
prevProps.current = props;
handlePageTransition(routeInfo);
} else if (pendingPageTransitionRef.current) {
handlePageTransition(routeInfo);
pendingPageTransitionRef.current = false;
} }
} }, [routeInfo]);
componentDidUpdate(prevProps: StackManagerProps) { useEffect(() => {
const { pathname } = this.props.routeInfo; return () => {
const { pathname: prevPathname } = prevProps.routeInfo; clearOutlet(id);
if (clearOutletTimeout.current) {
clearTimeout(clearOutletTimeout.current);
clearOutletTimeout.current = null;
}
};
}, []);
if (pathname !== prevPathname) { const handlePageTransition = (routeInfo: RouteInfo) => {
this.prevProps = prevProps; const routerOutletElement = routerOutletRef.current;
this.handlePageTransition(this.props.routeInfo); if (!routerOutletElement || !routerOutletElement.commit) {
} else if (this.pendingPageTransition) {
this.handlePageTransition(this.props.routeInfo);
this.pendingPageTransition = false;
}
}
componentWillUnmount() {
this.clearOutletTimeout = this.context.clearOutlet(this.id);
}
async handlePageTransition(routeInfo: RouteInfo) {
if (!this.routerOutletElement || !this.routerOutletElement.commit) {
/** /**
* The route outlet has not mounted yet. We need to wait for it to render * The route outlet has not mounted yet. We need to wait for it to render
* before we can transition the page. * before we can transition the page.
@@ -88,142 +106,126 @@ export class StackManager extends React.PureComponent<StackManagerProps, StackMa
* Set a flag to indicate that we should transition the page after * Set a flag to indicate that we should transition the page after
* the component has updated. * the component has updated.
*/ */
this.pendingPageTransition = true; pendingPageTransitionRef.current = true;
} else { return;
let enteringViewItem = this.context.findViewItemByRouteInfo(routeInfo, this.id); }
let leavingViewItem = this.context.findLeavingViewItemByRouteInfo(routeInfo, this.id);
if (!leavingViewItem && routeInfo.prevRouteLastPathname) { let enteringViewItem = findViewItemByRouteInfo(routeInfo, id);
leavingViewItem = this.context.findViewItemByPathname(routeInfo.prevRouteLastPathname, this.id); let leavingViewItem = findLeavingViewItemByRouteInfo(routeInfo, id);
}
// Check if leavingViewItem should be unmounted if (!leavingViewItem && routeInfo.prevRouteLastPathname) {
if (leavingViewItem) { leavingViewItem = findViewItemByPathname(routeInfo.prevRouteLastPathname, id);
if (routeInfo.routeAction === 'replace') { }
leavingViewItem.mount = false;
} else if (!(routeInfo.routeAction === 'push' && routeInfo.routeDirection === 'forward')) { // Check if the leavingViewItem should be unmounted
if (routeInfo.routeDirection !== 'none' && enteringViewItem !== leavingViewItem) { if (leavingViewItem) {
leavingViewItem.mount = false; if (routeInfo.routeAction === 'replace') {
} leavingViewItem.mount = false;
} else if (routeInfo.routeOptions?.unmount) { } else if (!(routeInfo.routeAction === 'push' && routeInfo.routeDirection === 'forward')) {
if (routeInfo.routeDirection !== 'none' && enteringViewItem !== leavingViewItem) {
leavingViewItem.mount = false; leavingViewItem.mount = false;
} }
} else if (routeInfo.routeOptions?.unmount) {
leavingViewItem.mount = false;
} }
}
const enteringRoute = matchRoute(this.ionRouterOutlet?.props.children, routeInfo) as React.ReactElement; const enteringRoute = matchRoute(ionRouterOutletRef.current?.props.children, routeInfo) as React.ReactElement;
if (enteringViewItem) { if (enteringViewItem) {
enteringViewItem.reactElement = enteringRoute; // If the entering view is already in the stack, then we need to clone it
} else if (enteringRoute) { enteringViewItem.reactElement = enteringRoute;
enteringViewItem = this.context.createViewItem(this.id, enteringRoute, routeInfo); } else if (enteringRoute) {
this.context.addViewItem(enteringViewItem); // Otherwise we need to create a new view item
} enteringViewItem = createViewItem(id, enteringRoute, routeInfo);
addViewItem(enteringViewItem);
}
if (enteringViewItem && enteringViewItem.ionPageElement) { if (enteringViewItem && enteringViewItem.ionPageElement) {
/**
* If the entering view item is the same as the leaving view item,
* then we don't need to transition.
*/
if (enteringViewItem === leavingViewItem) {
/** /**
* If the entering view item is the same as the leaving view item, * If the entering view item is the same as the leaving view item,
* then we don't need to transition. * we are either transitioning using parameterized routes to the same view
* or a parent router outlet is re-rendering as a result of React props changing.
*
* If the route data does not match the current path, the parent router outlet
* is attempting to transition and we cancel the operation.
*/ */
if (enteringViewItem === leavingViewItem) { if (enteringViewItem.routeData.match.url !== routeInfo.pathname) {
/**
* If the entering view item is the same as the leaving view item,
* we are either transitioning using parameterized routes to the same view
* or a parent router outlet is re-rendering as a result of React props changing.
*
* If the route data does not match the current path, the parent router outlet
* is attempting to transition and we cancel the operation.
*/
if (enteringViewItem.routeData.match.url !== routeInfo.pathname) {
return;
}
}
/**
* If there isn't a leaving view item, but the route info indicates
* that the user has routed from a previous path, then we need
* to find the leaving view item to transition between.
*/
if (!leavingViewItem && this.props.routeInfo.prevRouteLastPathname) {
leavingViewItem = this.context.findViewItemByPathname(this.props.routeInfo.prevRouteLastPathname, this.id);
}
/**
* If the entering view is already visible and the leaving view is not, the transition does not need to occur.
*/
if (
isViewVisible(enteringViewItem.ionPageElement) &&
leavingViewItem !== undefined &&
!isViewVisible(leavingViewItem.ionPageElement!)
) {
return; return;
} }
/**
* The view should only be transitioned in the following cases:
* 1. Performing a replace or pop action, such as a swipe to go back gesture
* to animation the leaving view off the screen.
*
* 2. Navigating between top-level router outlets, such as /page-1 to /page-2;
* or navigating within a nested outlet, such as /tabs/tab-1 to /tabs/tab-2.
*
* 3. The entering view is an ion-router-outlet containing a page
* matching the current route and that hasn't already transitioned in.
*
* This should only happen when navigating directly to a nested router outlet
* route or on an initial page load (i.e. refreshing). In cases when loading
* /tabs/tab-1, we need to transition the /tabs page element into the view.
*/
this.transitionPage(routeInfo, enteringViewItem, leavingViewItem);
} else if (leavingViewItem && !enteringRoute && !enteringViewItem) {
// If we have a leavingView but no entering view/route, we are probably leaving to
// another outlet, so hide this leavingView. We do it in a timeout to give time for a
// transition to finish.
// setTimeout(() => {
if (leavingViewItem.ionPageElement) {
leavingViewItem.ionPageElement.classList.add('ion-page-hidden');
leavingViewItem.ionPageElement.setAttribute('aria-hidden', 'true');
}
// }, 250);
} }
this.forceUpdate();
}
}
registerIonPage(page: HTMLElement, routeInfo: RouteInfo) {
const foundView = this.context.findViewItemByRouteInfo(routeInfo, this.id);
if (foundView) {
const oldPageElement = foundView.ionPageElement;
foundView.ionPageElement = page;
foundView.ionRoute = true;
/** /**
* React 18 will unmount and remount IonPage * If there isn't a leaving view item, but the route info indicates
* elements in development mode when using createRoot. * that the user has routed from a previous path, then we need
* This can cause duplicate page transitions to occur. * to find the leaving view item to transition between.
*/ */
if (oldPageElement === page) { if (!leavingViewItem && routeInfo.prevRouteLastPathname) {
leavingViewItem = findViewItemByPathname(routeInfo.prevRouteLastPathname, id);
}
/**
* If the entering view is already visible and the leaving view is not, the transition does not need to occur.
*/
if (
isViewVisible(enteringViewItem.ionPageElement) &&
leavingViewItem !== undefined &&
!isViewVisible(leavingViewItem.ionPageElement!)
) {
return; return;
} }
}
this.handlePageTransition(routeInfo);
}
async setupRouterOutlet(routerOutlet: HTMLIonRouterOutletElement) { /**
const canStart = () => { * The view should only be transitioned in the following cases:
* 1. Performing a replace or pop action, such as a swipe to go back gesture
* to animation the leaving view off the screen.
*
* 2. Navigating between top-level router outlets, such as /page-1 to /page-2;
* or navigating within a nested outlet, such as /tabs/tab-1 to /tabs/tab-2.
*
* 3. The entering view is an ion-router-outlet containing a page
* matching the current route and that hasn't already transitioned in.
*
* This should only happen when navigating directly to a nested router outlet
* route or on an initial page load (i.e. refreshing). In cases when loading
* /tabs/tab-1, we need to transition the /tabs page element into the view.
*/
transitionPage(routeInfo, enteringViewItem, leavingViewItem!);
} else if (leavingViewItem && !enteringRoute && !enteringViewItem) {
// If we have a leavingView but no entering view/route, we are probably leaving to
// another outlet, so hide this leavingView.
if (leavingViewItem.ionPageElement) {
leavingViewItem.ionPageElement.classList.add('ion-page-hidden');
leavingViewItem.ionPageElement.setAttribute('aria-hidden', 'true');
}
}
// This causes the router outlet to re-render with the updated view items.
// Without it, a push navigation will remove the previous route's view item,
// but will not render the new route's view item.
forceUpdate();
};
const setupRouterOutlet = (routerOutlet: HTMLIonRouterOutletElement) => {
const canStart = (): boolean => {
const config = getConfig(); const config = getConfig();
const swipeEnabled = config && config.get('swipeBackEnabled', routerOutlet.mode === 'ios'); const swipeEnabled = config?.getBoolean('swipeBackEnabled', routerOutlet.mode === 'ios');
if (!swipeEnabled) { if (!swipeEnabled) {
return false; return false;
} }
const { routeInfo } = this.props;
const propsToUse = const propsToUse =
this.prevProps && this.prevProps.routeInfo.pathname === routeInfo.pushedByRoute prevProps.current?.routeInfo.pathname === routeInfo.pushedByRoute
? this.prevProps.routeInfo ? prevProps.current!.routeInfo
: ({ pathname: routeInfo.pushedByRoute || '' } as any); : ({ pathname: routeInfo.pushedByRoute || '' } as any);
const enteringViewItem = this.context.findViewItemByRouteInfo(propsToUse, this.id, false);
const enteringViewItem = findViewItemByRouteInfo(propsToUse, id, false);
return ( return (
!!enteringViewItem && !!enteringViewItem &&
@@ -247,14 +249,12 @@ export class StackManager extends React.PureComponent<StackManagerProps, StackMa
}; };
const onStart = async () => { const onStart = async () => {
const { routeInfo } = this.props;
const propsToUse = const propsToUse =
this.prevProps && this.prevProps.routeInfo.pathname === routeInfo.pushedByRoute prevProps.current?.routeInfo.pathname === routeInfo.pushedByRoute
? this.prevProps.routeInfo ? prevProps.current!.routeInfo
: ({ pathname: routeInfo.pushedByRoute || '' } as any); : ({ pathname: routeInfo.pushedByRoute || '' } as any);
const enteringViewItem = this.context.findViewItemByRouteInfo(propsToUse, this.id, false); const enteringViewItem = findViewItemByRouteInfo(propsToUse, id, false);
const leavingViewItem = this.context.findViewItemByRouteInfo(routeInfo, this.id, false); const leavingViewItem = findViewItemByRouteInfo(routeInfo, id, false);
/** /**
* When the gesture starts, kick off * When the gesture starts, kick off
@@ -262,30 +262,28 @@ export class StackManager extends React.PureComponent<StackManagerProps, StackMa
* via a swipe gesture. * via a swipe gesture.
*/ */
if (enteringViewItem && leavingViewItem) { if (enteringViewItem && leavingViewItem) {
await this.transitionPage(routeInfo, enteringViewItem, leavingViewItem, 'back', true); await transitionPage(routeInfo, enteringViewItem, leavingViewItem, 'back', true);
} }
return Promise.resolve(); return Promise.resolve();
}; };
const onEnd = (shouldContinue: boolean) => { const onEnd = (shouldContinue: boolean) => {
if (shouldContinue) { if (shouldContinue) {
this.skipTransition = true; skipTransitionRef.current = true;
goBack();
this.context.goBack();
} else { } else {
/** /**
* In the event that the swipe * In the event that the swipe
* gesture was aborted, we should * gesture was aborted, we should
* re-hide the page that was going to enter. * re-hide the page that was going to enter.
*/ */
const { routeInfo } = this.props;
const propsToUse = const propsToUse =
this.prevProps && this.prevProps.routeInfo.pathname === routeInfo.pushedByRoute prevProps.current?.routeInfo.pathname === routeInfo.pushedByRoute
? this.prevProps.routeInfo ? prevProps.current!.routeInfo
: ({ pathname: routeInfo.pushedByRoute || '' } as any); : ({ pathname: routeInfo.pushedByRoute || '' } as any);
const enteringViewItem = this.context.findViewItemByRouteInfo(propsToUse, this.id, false); const enteringViewItem = findViewItemByRouteInfo(propsToUse, id, false);
const leavingViewItem = this.context.findViewItemByRouteInfo(routeInfo, this.id, false); const leavingViewItem = findViewItemByRouteInfo(routeInfo, id, false);
/** /**
* Ionic React has a design defect where it * Ionic React has a design defect where it
@@ -309,17 +307,17 @@ export class StackManager extends React.PureComponent<StackManagerProps, StackMa
onStart, onStart,
onEnd, onEnd,
}; };
} };
async transitionPage( const transitionPage = async (
routeInfo: RouteInfo, routeInfo: RouteInfo,
enteringViewItem: ViewItem, enteringViewItem: ViewItem,
leavingViewItem?: ViewItem, leavingViewItem: ViewItem,
direction?: 'forward' | 'back', direction?: 'forward' | 'back',
progressAnimation = false progressAnimation = false
) { ) => {
const runCommit = async (enteringEl: HTMLElement, leavingEl?: HTMLElement) => { const runCommit = async (enteringEl: HTMLElement, leavingEl?: HTMLElement) => {
const skipTransition = this.skipTransition; const skipTransition = skipTransitionRef.current;
/** /**
* If the transition was handled * If the transition was handled
@@ -342,102 +340,99 @@ export class StackManager extends React.PureComponent<StackManagerProps, StackMa
* transition triggered by handlePageTransition * transition triggered by handlePageTransition
* in componentDidUpdate. * in componentDidUpdate.
*/ */
this.skipTransition = false; skipTransitionRef.current = false;
} else { } else {
enteringEl.classList.add('ion-page'); enteringEl.classList.add('ion-page', 'ion-page-invisible');
enteringEl.classList.add('ion-page-invisible');
} }
await routerOutlet.commit(enteringEl, leavingEl, { const routerOutletElement = routerOutletRef.current;
duration: skipTransition || directionToUse === undefined ? 0 : undefined,
direction: directionToUse, if (routerOutletElement) {
showGoBack: !!routeInfo.pushedByRoute, await routerOutletElement.commit(enteringEl, leavingEl, {
progressAnimation, duration: skipTransitionRef.current || directionToUse === undefined ? 0 : undefined,
animationBuilder: routeInfo.routeAnimation, direction: directionToUse,
}); showGoBack: !!routeInfo.pushedByRoute,
progressAnimation,
animationBuilder: routeInfo.routeAnimation,
});
}
}; };
const routerOutlet = this.routerOutletElement!; const routerInfoFallbackDirection =
const routeInfoFallbackDirection =
routeInfo.routeDirection === 'none' || routeInfo.routeDirection === 'root' ? undefined : routeInfo.routeDirection; routeInfo.routeDirection === 'none' || routeInfo.routeDirection === 'root' ? undefined : routeInfo.routeDirection;
const directionToUse = direction ?? routeInfoFallbackDirection; const directionToUse = direction ?? routerInfoFallbackDirection;
if (enteringViewItem && enteringViewItem.ionPageElement && this.routerOutletElement) { const routerOutletElement = routerOutletRef.current;
if (leavingViewItem && leavingViewItem.ionPageElement && enteringViewItem === leavingViewItem) {
if (enteringViewItem?.ionPageElement && routerOutletElement) {
if (leavingViewItem?.ionPageElement && enteringViewItem === leavingViewItem) {
// If a page is transitioning to another version of itself // If a page is transitioning to another version of itself
// we clone it so we can have an animation to show // we clone it so we can have an animation to show
const match = matchComponent(leavingViewItem.reactElement, routeInfo.pathname, true); const match = matchComponent(leavingViewItem.reactElement, routeInfo.pathname, true);
if (match) { if (match) {
const newLeavingElement = clonePageElement(leavingViewItem.ionPageElement.outerHTML); const newLeavingElement = clonePageElement(leavingViewItem.ionPageElement.outerHTML);
if (newLeavingElement) { if (newLeavingElement) {
this.routerOutletElement.appendChild(newLeavingElement); routerOutletElement.appendChild(newLeavingElement);
await runCommit(enteringViewItem.ionPageElement, newLeavingElement); await runCommit(enteringViewItem.ionPageElement, newLeavingElement);
this.routerOutletElement.removeChild(newLeavingElement); routerOutletElement.removeChild(newLeavingElement);
} }
} else { } else {
await runCommit(enteringViewItem.ionPageElement, undefined); await runCommit(enteringViewItem.ionPageElement, undefined);
} }
} else { } else {
await runCommit(enteringViewItem.ionPageElement, leavingViewItem?.ionPageElement); await runCommit(enteringViewItem.ionPageElement, leavingViewItem?.ionPageElement);
if (leavingViewItem && leavingViewItem.ionPageElement && !progressAnimation) { if (leavingViewItem?.ionPageElement && !progressAnimation) {
leavingViewItem.ionPageElement.classList.add('ion-page-hidden'); const { ionPageElement } = leavingViewItem;
leavingViewItem.ionPageElement.setAttribute('aria-hidden', 'true'); ionPageElement.setAttribute('aria-hidden', 'true');
ionPageElement.classList.add('ion-page-hidden');
} }
} }
} }
} };
render() { const renderComponents = () => {
const { children } = this.props; const ionRouterOutlet = React.Children.only<React.ReactElement>(children as React.ReactElement);
const ionRouterOutlet = React.Children.only(children) as React.ReactElement; ionRouterOutletRef.current = ionRouterOutlet;
this.ionRouterOutlet = ionRouterOutlet;
const components = this.context.getChildrenToRender(this.id, this.ionRouterOutlet, this.props.routeInfo, () => { const components = getChildrenToRender(id, ionRouterOutlet, routeInfo, () => {
this.forceUpdate(); forceUpdate();
}); });
return ( return cloneElement(ionRouterOutlet, {
<StackContext.Provider value={this.stackContextValue}> ref: (node: HTMLIonRouterOutletElement) => {
{React.cloneElement( if (ionRouterOutlet.props.setRef) {
ionRouterOutlet as any, ionRouterOutlet.props.setRef(node);
{ }
ref: (node: HTMLIonRouterOutletElement) => { if (ionRouterOutlet.props.forwardedRef) {
if (ionRouterOutlet.props.setRef) { ionRouterOutlet.props.forwardedRef.current = node;
ionRouterOutlet.props.setRef(node); }
}
if (ionRouterOutlet.props.forwardedRef) {
ionRouterOutlet.props.forwardedRef.current = node;
}
this.routerOutletElement = node;
const { ref } = ionRouterOutlet as any;
if (typeof ref === 'function') {
ref(node);
}
},
},
components
)}
</StackContext.Provider>
);
}
static get contextType() { routerOutletRef.current = node;
return RouteManagerContext;
} const { ref } = ionRouterOutlet as any;
} if (typeof ref === 'function') {
ref(node);
}
},
components,
});
};
return <StackContext.Provider value={stackContextValue}>{renderComponents()}</StackContext.Provider>;
};
export default StackManager; export default StackManager;
function matchRoute(node: React.ReactNode, routeInfo: RouteInfo) { function matchRoute(node: React.ReactNode, routeInfo: RouteInfo) {
let matchedNode: React.ReactNode; let matchedNode: React.ReactNode;
React.Children.forEach(node as React.ReactElement, (child: React.ReactElement) => { React.Children.forEach(node as React.ReactElement, (child: React.ReactElement) => {
const matchProps = { const matchProps = {
exact: child.props.exact, exact: child.props.exact,
path: child.props.path || child.props.from, path: child.props.path || child.props.from,
component: child.props.component, component: child.props.component,
}; };
// In React Router v6 the prop arguments are in a different order
const match = matchPath(routeInfo.pathname, matchProps); const match = matchPath(routeInfo.pathname, matchProps);
if (match) { if (match) {
matchedNode = child; matchedNode = child;
@@ -464,6 +459,7 @@ function matchComponent(node: React.ReactElement, pathname: string, forceExact?:
path: node.props.path || node.props.from, path: node.props.path || node.props.from,
component: node.props.component, component: node.props.component,
}; };
// In React Router v6 the prop arguments are in a different order
const match = matchPath(pathname, matchProps); const match = matchPath(pathname, matchProps);
return match; return match;