mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-16 10:01:59 +08:00
fix(react): Keep a hold of previous routes when doing a redirect, closes #22053
This commit is contained in:
@ -41,7 +41,7 @@ class IonRouterInner extends React.PureComponent<IonRouteProps, IonRouteState> {
|
|||||||
routeMangerContextState: RouteManagerContextState = {
|
routeMangerContextState: RouteManagerContextState = {
|
||||||
canGoBack: () => this.locationHistory.canGoBack(),
|
canGoBack: () => this.locationHistory.canGoBack(),
|
||||||
clearOutlet: this.viewStack.clear,
|
clearOutlet: this.viewStack.clear,
|
||||||
getViewItemForTransition: this.viewStack.getViewItemForTransition,
|
findViewItemByPathname: this.viewStack.findViewItemByPathname,
|
||||||
getChildrenToRender: this.viewStack.getChildrenToRender,
|
getChildrenToRender: this.viewStack.getChildrenToRender,
|
||||||
goBack: () => this.handleNavigateBack(),
|
goBack: () => this.handleNavigateBack(),
|
||||||
createViewItem: this.viewStack.createViewItem,
|
createViewItem: this.viewStack.createViewItem,
|
||||||
@ -154,7 +154,8 @@ class IonRouterInner extends React.PureComponent<IonRouteProps, IonRouteState> {
|
|||||||
lastPathname: leavingLocationInfo.pathname,
|
lastPathname: leavingLocationInfo.pathname,
|
||||||
pathname: location.pathname,
|
pathname: location.pathname,
|
||||||
search: location.search,
|
search: location.search,
|
||||||
params: this.props.match.params
|
params: this.props.match.params,
|
||||||
|
prevRouteLastPathname: leavingLocationInfo.lastPathname
|
||||||
};
|
};
|
||||||
if (isPushed) {
|
if (isPushed) {
|
||||||
routeInfo.tab = leavingLocationInfo.tab;
|
routeInfo.tab = leavingLocationInfo.tab;
|
||||||
@ -170,6 +171,7 @@ class IonRouterInner extends React.PureComponent<IonRouteProps, IonRouteState> {
|
|||||||
// Make sure to set the lastPathname, etc.. to the current route so the page transitions out
|
// Make sure to set the lastPathname, etc.. to the current route so the page transitions out
|
||||||
const currentRouteInfo = this.locationHistory.current();
|
const currentRouteInfo = this.locationHistory.current();
|
||||||
routeInfo.lastPathname = currentRouteInfo?.pathname || routeInfo.lastPathname;
|
routeInfo.lastPathname = currentRouteInfo?.pathname || routeInfo.lastPathname;
|
||||||
|
routeInfo.prevRouteLastPathname = currentRouteInfo?.lastPathname;
|
||||||
routeInfo.pushedByRoute = currentRouteInfo?.pushedByRoute || routeInfo.pushedByRoute;
|
routeInfo.pushedByRoute = currentRouteInfo?.pushedByRoute || routeInfo.pushedByRoute;
|
||||||
routeInfo.routeDirection = currentRouteInfo?.routeDirection || routeInfo.routeDirection;
|
routeInfo.routeDirection = currentRouteInfo?.routeDirection || routeInfo.routeDirection;
|
||||||
routeInfo.routeAnimation = currentRouteInfo?.routeAnimation || routeInfo.routeAnimation;
|
routeInfo.routeAnimation = currentRouteInfo?.routeAnimation || routeInfo.routeAnimation;
|
||||||
@ -187,13 +189,13 @@ class IonRouterInner extends React.PureComponent<IonRouteProps, IonRouteState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleNavigate(path: string, routeAction: RouteAction, routeDirection?: RouterDirection, routeAnimation?: AnimationBuilder, routeOptions?: any, tab?: string) {
|
handleNavigate(path: string, routeAction: RouteAction, routeDirection?: RouterDirection, routeAnimation?: AnimationBuilder, routeOptions?: any, tab?: string) {
|
||||||
this.incomingRouteParams = {
|
this.incomingRouteParams = Object.assign(this.incomingRouteParams || {}, {
|
||||||
routeAction,
|
routeAction,
|
||||||
routeDirection,
|
routeDirection,
|
||||||
routeOptions,
|
routeOptions,
|
||||||
routeAnimation,
|
routeAnimation,
|
||||||
tab
|
tab
|
||||||
};
|
});
|
||||||
|
|
||||||
if (routeAction === 'push') {
|
if (routeAction === 'push') {
|
||||||
this.props.history.push(path);
|
this.props.history.push(path);
|
||||||
@ -213,7 +215,7 @@ class IonRouterInner extends React.PureComponent<IonRouteProps, IonRouteState> {
|
|||||||
if (routeInfo.lastPathname === routeInfo.pushedByRoute) {
|
if (routeInfo.lastPathname === routeInfo.pushedByRoute) {
|
||||||
this.props.history.goBack();
|
this.props.history.goBack();
|
||||||
} else {
|
} else {
|
||||||
this.props.history.replace(prevInfo.pathname + (prevInfo.search || ''));
|
this.handleNavigate(prevInfo.pathname + (prevInfo.search || ''), 'pop', 'back');
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.handleNavigate(defaultHref as string, 'pop', 'back');
|
this.handleNavigate(defaultHref as string, 'pop', 'back');
|
||||||
|
@ -10,7 +10,7 @@ export class ReactRouterViewStack extends ViewStacks {
|
|||||||
this.findViewItemByRouteInfo = this.findViewItemByRouteInfo.bind(this);
|
this.findViewItemByRouteInfo = this.findViewItemByRouteInfo.bind(this);
|
||||||
this.findLeavingViewItemByRouteInfo = this.findLeavingViewItemByRouteInfo.bind(this);
|
this.findLeavingViewItemByRouteInfo = this.findLeavingViewItemByRouteInfo.bind(this);
|
||||||
this.getChildrenToRender = this.getChildrenToRender.bind(this);
|
this.getChildrenToRender = this.getChildrenToRender.bind(this);
|
||||||
this.getViewItemForTransition = this.getViewItemForTransition.bind(this);
|
this.findViewItemByPathname = this.findViewItemByPathname.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
createViewItem(outletId: string, reactElement: React.ReactElement, routeInfo: RouteInfo, page?: HTMLElement) {
|
createViewItem(outletId: string, reactElement: React.ReactElement, routeInfo: RouteInfo, page?: HTMLElement) {
|
||||||
@ -97,13 +97,13 @@ export class ReactRouterViewStack extends ViewStacks {
|
|||||||
return viewItem;
|
return viewItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
findLeavingViewItemByRouteInfo(routeInfo: RouteInfo, outletId?: string) {
|
findLeavingViewItemByRouteInfo(routeInfo: RouteInfo, outletId?: string, mustBeIonRoute = true) {
|
||||||
const { viewItem } = this.findViewItemByPath(routeInfo.lastPathname!, outletId, false, true);
|
const { viewItem } = this.findViewItemByPath(routeInfo.lastPathname!, outletId, false, mustBeIonRoute);
|
||||||
return viewItem;
|
return viewItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
getViewItemForTransition(pathname: string) {
|
findViewItemByPathname(pathname: string, outletId?: string) {
|
||||||
const { viewItem } = this.findViewItemByPath(pathname, undefined, true, true);
|
const { viewItem } = this.findViewItemByPath(pathname, outletId);
|
||||||
return viewItem;
|
return viewItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,7 +63,11 @@ export class StackManager extends React.PureComponent<StackManagerProps, StackMa
|
|||||||
setTimeout(() => this.handlePageTransition(routeInfo), 10);
|
setTimeout(() => this.handlePageTransition(routeInfo), 10);
|
||||||
} else {
|
} else {
|
||||||
let enteringViewItem = this.context.findViewItemByRouteInfo(routeInfo, this.id);
|
let enteringViewItem = this.context.findViewItemByRouteInfo(routeInfo, this.id);
|
||||||
const leavingViewItem = this.context.findLeavingViewItemByRouteInfo(routeInfo, this.id);
|
let leavingViewItem = this.context.findLeavingViewItemByRouteInfo(routeInfo, this.id);
|
||||||
|
|
||||||
|
if (!leavingViewItem && routeInfo.prevRouteLastPathname) {
|
||||||
|
leavingViewItem = this.context.findViewItemByPathname(routeInfo.prevRouteLastPathname, this.id);
|
||||||
|
}
|
||||||
|
|
||||||
// Check if leavingViewItem should be unmounted
|
// Check if leavingViewItem should be unmounted
|
||||||
if (leavingViewItem) {
|
if (leavingViewItem) {
|
||||||
@ -76,7 +80,6 @@ export class StackManager extends React.PureComponent<StackManagerProps, StackMa
|
|||||||
} else if (routeInfo.routeOptions?.unmount) {
|
} else if (routeInfo.routeOptions?.unmount) {
|
||||||
leavingViewItem.mount = false;
|
leavingViewItem.mount = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const enteringRoute = matchRoute(this.ionRouterOutlet?.props.children, routeInfo) as React.ReactElement;
|
const enteringRoute = matchRoute(this.ionRouterOutlet?.props.children, routeInfo) as React.ReactElement;
|
||||||
@ -95,12 +98,12 @@ export class StackManager extends React.PureComponent<StackManagerProps, StackMa
|
|||||||
// If we have a leavingView but no entering view/route, we are probably leaving to
|
// 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
|
// another outlet, so hide this leavingView. We do it in a timeout to give time for a
|
||||||
// transition to finish.
|
// transition to finish.
|
||||||
setTimeout(() => {
|
// setTimeout(() => {
|
||||||
if (leavingViewItem.ionPageElement) {
|
if (leavingViewItem.ionPageElement) {
|
||||||
leavingViewItem.ionPageElement.classList.add('ion-page-hidden');
|
leavingViewItem.ionPageElement.classList.add('ion-page-hidden');
|
||||||
leavingViewItem.ionPageElement.setAttribute('aria-hidden', 'true');
|
leavingViewItem.ionPageElement.setAttribute('aria-hidden', 'true');
|
||||||
}
|
}
|
||||||
}, 250);
|
// }, 250);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.forceUpdate();
|
this.forceUpdate();
|
||||||
|
@ -269,6 +269,28 @@ describe('Routing Tests', () => {
|
|||||||
cy.ionTabClick('Home')
|
cy.ionTabClick('Home')
|
||||||
cy.ionPageVisible('home-details-page-2')
|
cy.ionPageVisible('home-details-page-2')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('/routing/tabs/home Menu > Favorites > Menu > Home with redirect, Home page should be visible, and Favorites should be hidden', () => {
|
||||||
|
cy.visit(`http://localhost:${port}/routing/tabs/home`)
|
||||||
|
cy.ionMenuClick()
|
||||||
|
cy.ionMenuNav('Favorites')
|
||||||
|
cy.ionPageVisible('favorites-page')
|
||||||
|
cy.ionMenuClick()
|
||||||
|
cy.ionMenuNav('Home with redirect')
|
||||||
|
cy.ionPageVisible('home-page')
|
||||||
|
cy.ionPageDoesNotExist('favorites-page')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('/routing/tabs/home Menu > Favorites > Menu > Home with router, Home page should be visible, and Favorites should be hidden', () => {
|
||||||
|
cy.visit(`http://localhost:${port}/routing/tabs/home`)
|
||||||
|
cy.ionMenuClick()
|
||||||
|
cy.ionMenuNav('Favorites')
|
||||||
|
cy.ionPageVisible('favorites-page')
|
||||||
|
cy.ionMenuClick()
|
||||||
|
cy.ionMenuNav('Home with router')
|
||||||
|
cy.ionPageVisible('home-page')
|
||||||
|
cy.ionPageHidden('favorites-page')
|
||||||
|
})
|
||||||
/*
|
/*
|
||||||
Tests to add:
|
Tests to add:
|
||||||
Test that lifecycle events fire
|
Test that lifecycle events fire
|
||||||
|
@ -47,11 +47,16 @@ Cypress.Commands.add('ionPageVisible', (pageId) => {
|
|||||||
// cy.get(`div.ion-page[data-pageid=${pageId}]`).should('have.attr', 'style', 'z-index: 101;')
|
// cy.get(`div.ion-page[data-pageid=${pageId}]`).should('have.attr', 'style', 'z-index: 101;')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
Cypress.Commands.add('ionPageInvisible', (pageId) => {
|
||||||
|
cy.get(`div.ion-page[data-pageid=${pageId}]`)
|
||||||
|
.should('have.class', 'ion-page-invisible')
|
||||||
|
.should('have.length', 1)
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
Cypress.Commands.add('ionPageHidden', (pageId) => {
|
Cypress.Commands.add('ionPageHidden', (pageId) => {
|
||||||
cy.get(`div.ion-page[data-pageid=${pageId}]`)
|
cy.get(`div.ion-page[data-pageid=${pageId}]`)
|
||||||
.should('have.class', 'ion-page-hidden')
|
.should('have.class', 'ion-page-hidden')
|
||||||
.should('have.class', 'ion-page-invisible')
|
|
||||||
.should('have.length', 1)
|
.should('have.length', 1)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@ const ReplaceAction: React.FC<TopPageProps> = () => {
|
|||||||
<IonRouterOutlet>
|
<IonRouterOutlet>
|
||||||
<Route path="/replace-action/page1" component={Page1} exact />
|
<Route path="/replace-action/page1" component={Page1} exact />
|
||||||
<Route path="/replace-action/page2" component={Page2} exact />
|
<Route path="/replace-action/page2" component={Page2} exact />
|
||||||
<Route path="/replace-action/page3" component={Edit} exact />
|
<Route path="/replace-action/page3" component={Page3} exact />
|
||||||
<Route exact path="/replace-action" render={() => <Redirect to="/replace-action/page1" />} />
|
<Route exact path="/replace-action" render={() => <Redirect to="/replace-action/page1" />} />
|
||||||
</IonRouterOutlet>
|
</IonRouterOutlet>
|
||||||
);
|
);
|
||||||
@ -61,7 +61,7 @@ const Page2: React.FC = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const Edit: React.FC = () => {
|
const Page3: React.FC = () => {
|
||||||
return (
|
return (
|
||||||
<IonPage data-pageid="page3">
|
<IonPage data-pageid="page3">
|
||||||
<IonHeader>
|
<IonHeader>
|
||||||
|
@ -41,7 +41,19 @@ const appPages: AppPage[] = [
|
|||||||
url: '/routing/otherpage',
|
url: '/routing/otherpage',
|
||||||
iosIcon: heartOutline,
|
iosIcon: heartOutline,
|
||||||
mdIcon: heartSharp
|
mdIcon: heartSharp
|
||||||
}
|
},
|
||||||
|
{
|
||||||
|
title: 'Home with redirect',
|
||||||
|
url: '/routing/redirect',
|
||||||
|
iosIcon: heartOutline,
|
||||||
|
mdIcon: heartSharp
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Home with router',
|
||||||
|
url: '/routing/redirect-routing',
|
||||||
|
iosIcon: heartOutline,
|
||||||
|
mdIcon: heartSharp
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const Menu: React.FunctionComponent<MenuProps> = () => {
|
const Menu: React.FunctionComponent<MenuProps> = () => {
|
||||||
@ -67,4 +79,4 @@ const Menu: React.FunctionComponent<MenuProps> = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Menu;
|
export default Menu;
|
||||||
|
@ -0,0 +1,12 @@
|
|||||||
|
import React, { useEffect, useContext } from 'react';
|
||||||
|
import { IonRouterContext } from '@ionic/react';
|
||||||
|
|
||||||
|
const RedirectRouting: React.FC= () => {
|
||||||
|
const ionRouterContext = useContext(IonRouterContext);
|
||||||
|
useEffect(() => {
|
||||||
|
ionRouterContext.push('/routing/tabs', 'none')
|
||||||
|
}, []);
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default RedirectRouting;
|
@ -6,6 +6,7 @@ import Tabs from './Tabs';
|
|||||||
import Favorites from './Favorites';
|
import Favorites from './Favorites';
|
||||||
import OtherPage from './OtherPage';
|
import OtherPage from './OtherPage';
|
||||||
import PropsTest from './PropsTest';
|
import PropsTest from './PropsTest';
|
||||||
|
import RedirectRouting from './RedirectRouting';
|
||||||
|
|
||||||
interface RoutingProps {
|
interface RoutingProps {
|
||||||
}
|
}
|
||||||
@ -35,6 +36,8 @@ const Routing: React.FC<RoutingProps> = () => {
|
|||||||
}} /> */}
|
}} /> */}
|
||||||
<Route path="/routing/otherpage" component={OtherPage} />
|
<Route path="/routing/otherpage" component={OtherPage} />
|
||||||
<Route path="/routing/propstest" component={PropsTest} />
|
<Route path="/routing/propstest" component={PropsTest} />
|
||||||
|
<Route path="/routing/redirect" render={() => <Redirect to="/routing/tabs" />} />
|
||||||
|
<Route path="/routing/redirect-routing" render={() => <RedirectRouting />} />
|
||||||
<Route render={() => <IonPage data-pageid="not-found"><IonContent><div>Not found</div></IonContent></IonPage>} />
|
<Route render={() => <IonPage data-pageid="not-found"><IonContent><div>Not found</div></IonContent></IonPage>} />
|
||||||
{/* <Route render={() => <Redirect to="/tabs" />} /> */}
|
{/* <Route render={() => <Redirect to="/tabs" />} /> */}
|
||||||
|
|
||||||
|
@ -33,6 +33,7 @@
|
|||||||
"no-unused-expression": false,
|
"no-unused-expression": false,
|
||||||
"no-constant-condition": false,
|
"no-constant-condition": false,
|
||||||
"no-empty-interface": false,
|
"no-empty-interface": false,
|
||||||
"prefer-conditional-expression": false
|
"prefer-conditional-expression": false,
|
||||||
|
"prefer-object-spread": false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ import { RouterDirection } from './RouterDirection';
|
|||||||
export interface RouteInfo<TOptions = any> {
|
export interface RouteInfo<TOptions = any> {
|
||||||
id: string;
|
id: string;
|
||||||
lastPathname?: string;
|
lastPathname?: string;
|
||||||
|
prevRouteLastPathname?: string;
|
||||||
routeAction?: RouteAction;
|
routeAction?: RouteAction;
|
||||||
routeDirection?: RouterDirection;
|
routeDirection?: RouterDirection;
|
||||||
routeAnimation?: AnimationBuilder;
|
routeAnimation?: AnimationBuilder;
|
||||||
|
@ -9,10 +9,10 @@ export interface RouteManagerContextState {
|
|||||||
canGoBack: () => boolean;
|
canGoBack: () => boolean;
|
||||||
clearOutlet: (outletId: string) => void;
|
clearOutlet: (outletId: string) => void;
|
||||||
createViewItem: (outletId: string, reactElement: React.ReactElement, routeInfo: RouteInfo, page?: HTMLElement) => ViewItem;
|
createViewItem: (outletId: string, reactElement: React.ReactElement, routeInfo: RouteInfo, page?: HTMLElement) => ViewItem;
|
||||||
|
findViewItemByPathname(pathname: string, outletId?: string): ViewItem | undefined;
|
||||||
findLeavingViewItemByRouteInfo: (routeInfo: RouteInfo, outletId?: string) => ViewItem | undefined;
|
findLeavingViewItemByRouteInfo: (routeInfo: RouteInfo, outletId?: string) => ViewItem | undefined;
|
||||||
findViewItemByRouteInfo: (routeInfo: RouteInfo, outletId?: string) => ViewItem | undefined;
|
findViewItemByRouteInfo: (routeInfo: RouteInfo, outletId?: string) => ViewItem | undefined;
|
||||||
getChildrenToRender: (outletId: string, ionRouterOutlet: React.ReactElement, routeInfo: RouteInfo, reRender: () => void) => React.ReactNode[];
|
getChildrenToRender: (outletId: string, ionRouterOutlet: React.ReactElement, routeInfo: RouteInfo, reRender: () => void) => React.ReactNode[];
|
||||||
getViewItemForTransition: (pathname: string) => ViewItem | undefined;
|
|
||||||
goBack: () => void;
|
goBack: () => void;
|
||||||
unMountViewItem: (viewItem: ViewItem) => void;
|
unMountViewItem: (viewItem: ViewItem) => void;
|
||||||
}
|
}
|
||||||
@ -22,10 +22,10 @@ export const RouteManagerContext = /*@__PURE__*/React.createContext<RouteManager
|
|||||||
canGoBack: () => undefined as any,
|
canGoBack: () => undefined as any,
|
||||||
clearOutlet: () => undefined,
|
clearOutlet: () => undefined,
|
||||||
createViewItem: () => undefined as any,
|
createViewItem: () => undefined as any,
|
||||||
|
findViewItemByPathname: () => undefined,
|
||||||
findLeavingViewItemByRouteInfo: () => undefined,
|
findLeavingViewItemByRouteInfo: () => undefined,
|
||||||
findViewItemByRouteInfo: () => undefined,
|
findViewItemByRouteInfo: () => undefined,
|
||||||
getChildrenToRender: () => undefined as any,
|
getChildrenToRender: () => undefined as any,
|
||||||
getViewItemForTransition: () => undefined,
|
|
||||||
goBack: () => undefined,
|
goBack: () => undefined,
|
||||||
unMountViewItem: () => undefined,
|
unMountViewItem: () => undefined,
|
||||||
});
|
});
|
||||||
|
@ -59,9 +59,8 @@ export abstract class ViewStacks {
|
|||||||
}
|
}
|
||||||
|
|
||||||
abstract createViewItem(outletId: string, reactElement: React.ReactElement, routeInfo: RouteInfo, page?: HTMLElement): ViewItem;
|
abstract createViewItem(outletId: string, reactElement: React.ReactElement, routeInfo: RouteInfo, page?: HTMLElement): ViewItem;
|
||||||
// abstract findViewItemByPathname(pathname: string, outletId?: string): ViewItem | undefined;
|
abstract findViewItemByPathname(pathname: string, outletId?: string): ViewItem | undefined;
|
||||||
abstract findViewItemByRouteInfo(routeInfo: RouteInfo, outletId?: string): ViewItem | undefined;
|
abstract findViewItemByRouteInfo(routeInfo: RouteInfo, outletId?: string): ViewItem | undefined;
|
||||||
abstract findLeavingViewItemByRouteInfo(routeInfo: RouteInfo, outletId?: string): ViewItem | undefined;
|
abstract findLeavingViewItemByRouteInfo(routeInfo: RouteInfo, outletId?: string): ViewItem | undefined;
|
||||||
abstract getChildrenToRender(outletId: string, ionRouterOutlet: React.ReactElement, routeInfo: RouteInfo, reRender: () => void, setInTransition: () => void): React.ReactNode[];
|
abstract getChildrenToRender(outletId: string, ionRouterOutlet: React.ReactElement, routeInfo: RouteInfo, reRender: () => void, setInTransition: () => void): React.ReactNode[];
|
||||||
abstract getViewItemForTransition(pathname: string): ViewItem | undefined;
|
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user