fix(react): Keep a hold of previous routes when doing a redirect, closes #22053

This commit is contained in:
Ely Lucas
2020-09-08 19:58:00 -06:00
committed by GitHub
parent ba23ab3d66
commit 74af3cb50b
13 changed files with 88 additions and 28 deletions

View File

@ -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');

View File

@ -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;
} }

View File

@ -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();

View File

@ -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

View File

@ -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)
}) })

View File

@ -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>

View File

@ -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;

View File

@ -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;

View File

@ -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" />} /> */}

View File

@ -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
} }
} }

View File

@ -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;

View File

@ -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,
}); });

View File

@ -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;
} }