Skip to content

Commit

Permalink
fix(react): nested router outlets will not reanimate entered views (#…
Browse files Browse the repository at this point in the history
…24672)

Resolves #24107
  • Loading branch information
sean-perkins committed Jan 31, 2022
1 parent 484de50 commit 43aa6c1
Show file tree
Hide file tree
Showing 7 changed files with 94 additions and 84 deletions.
62 changes: 56 additions & 6 deletions packages/react-router/src/ReactRouter/StackManager.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ interface StackManagerProps {

interface StackManagerState {}

const isViewVisible = (el: HTMLElement) => !el.classList.contains('ion-page-invisible') && !el.classList.contains('ion-page-hidden');

export class StackManager extends React.PureComponent<StackManagerProps, StackManagerState> {
id: string;
context!: React.ContextType<typeof RouteManagerContext>;
Expand Down Expand Up @@ -97,16 +99,64 @@ export class StackManager extends React.PureComponent<StackManagerProps, StackMa
this.ionRouterOutlet?.props.children,
routeInfo
) as React.ReactElement;

if (enteringViewItem) {
enteringViewItem.reactElement = enteringRoute;
} else if (enteringRoute) {
enteringViewItem = this.context.createViewItem(this.id, enteringRoute, routeInfo);
this.context.addViewItem(enteringViewItem);
}
if (!enteringViewItem) {
if (enteringRoute) {
enteringViewItem = this.context.createViewItem(this.id, enteringRoute, routeInfo);
this.context.addViewItem(enteringViewItem);
}
}

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,
* 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;
}

/**
* 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ describe('Nested Outlets 2', () => {
Utilizes `ionPage` prop on `IonRouterOutlet` to make the router outlet
a target of the transition.
This one uses a few more nested outlets.
Note: There is a limitation when going back to the Home page from Welcome in
Note: There is a limitation when going back to the Home page from Welcome in
that the transition doesn't do a backwards animation. This is because the top level
outlet is transition from and to itself, therefore it can't animate.
I think the same issue exists when going from a item page back to the list page.
Expand Down Expand Up @@ -83,12 +83,12 @@ describe('Nested Outlets 2', () => {
cy.ionPageVisible('list');
});

it(`/nested-outlet2 >
it(`/nested-outlet2 >
Go to Welcome IonItem click >
Go to list from Welcome IonItem click >
Go to list from Welcome IonItem click >
Item#1 IonItem Click >
Item page should be visible
`, () => {
Item page should be visible
`, () => {
cy.visit(`http://localhost:${port}/nested-outlet2`);
cy.ionPageVisible('home');
cy.ionNav('ion-item', 'Go to Welcome');
Expand All @@ -99,13 +99,32 @@ describe('Nested Outlets 2', () => {
cy.ionPageVisible('item');
});

it(`/nested-outlet2 >
it(`/nested-outlet2 >
Go to list from Home IonItem click >
Item#1 IonItem Click >
Item page should be visible >
Item page should be visible >
Back >
List page should be visible
`, () => {
cy.visit(`http://localhost:${port}/nested-outlet2`);
cy.ionPageVisible('home');
cy.ionNav('ion-item', 'Go to list from Home');
cy.ionPageVisible('list');
cy.ionNav('ion-item', 'Item #1');
cy.ionPageVisible('item');
cy.ionBackClick('item');
cy.ionPageVisible('list');
});

it(`/nested-outlet2 >
Go to list from Home IonItem click >
Item#1 IonItem Click >
Item page should be visible >
Back >
List page should be visible
Back >
Home page should be visible
`, () => {
`, () => {
cy.visit(`http://localhost:${port}/nested-outlet2`);
cy.ionPageVisible('home');
cy.ionNav('ion-item', 'Go to list from Home');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@ describe('Routing Tests', () => {
cy.get('div[data-testid=count-label]').contains('3');
});

it('/asdf, when accessing a route not defined from root outlet, should show not found page', () => {
it('/routing/asdf, when accessing a route not defined from root outlet, should show not found page', () => {
cy.visit(`http://localhost:${port}/routing/asdf`, { failOnStatusCode: false });
cy.ionPageVisible('not-found');
cy.get('div').contains('Not found');
Expand Down
2 changes: 1 addition & 1 deletion packages/react/src/components/IonRouterContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export const IonRouterContext = React.createContext<IonRouterContextState>({
});

/**
* A hook for more direct control over routing in an Ionic React applicaiton. Allows you to pass additional meta-data to the router before the call to the native router.
* A hook for more direct control over routing in an Ionic React application. Allows you to pass additional meta-data to the router before the call to the native router.
*/
export function useIonRouter(): UseIonRouterResult {
const context = useContext(IonRouterContext);
Expand Down
5 changes: 3 additions & 2 deletions packages/react/src/routing/OutletPageManager.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { componentOnReady } from '@ionic/core';
import React from 'react';

import { IonRouterOutletInner } from '../components/inner-proxies';
Expand All @@ -24,9 +25,9 @@ export class OutletPageManager extends React.Component<OutletPageManagerProps> {

componentDidMount() {
if (this.ionRouterOutlet) {
setTimeout(() => {
componentOnReady(this.ionRouterOutlet, () => {
this.context.registerIonPage(this.ionRouterOutlet!, this.props.routeInfo!);
}, 25);
});

this.ionRouterOutlet.addEventListener(
'ionViewWillEnter',
Expand Down
12 changes: 6 additions & 6 deletions packages/react/test-app/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react';
import { Route } from 'react-router-dom';
import { IonApp, setupIonicReact } from '@ionic/react';
import { IonApp, IonButton, IonContent, IonHeader, IonPage, IonRouterOutlet, IonTitle, IonToolbar, setupIonicReact } from '@ionic/react';
import { IonReactRouter } from '@ionic/react-router';

/* Core CSS required for Ionic components to work properly */
Expand All @@ -24,17 +24,17 @@ import './theme/variables.css';
import Main from './pages/Main';
import OverlayHooks from './pages/overlay-hooks/OverlayHooks';
import OverlayComponents from './pages/overlay-components/OverlayComponents';
import Tabs from './pages/Tabs';

setupIonicReact();

const App: React.FC = () => (
<IonApp>
<IonReactRouter>
<Route path="/" component={Main} />
<Route path="/overlay-hooks" component={OverlayHooks} />
<Route path="/overlay-components" component={OverlayComponents} />
<Route path="/tabs" component={Tabs} />
<IonRouterOutlet>
<Route path="/" component={Main} />
<Route path="/overlay-hooks" component={OverlayHooks} />
<Route path="/overlay-components" component={OverlayComponents} />
</IonRouterOutlet>
</IonReactRouter>
</IonApp>
);
Expand Down
60 changes: 0 additions & 60 deletions packages/react/test-app/src/pages/Tabs.tsx

This file was deleted.

0 comments on commit 43aa6c1

Please sign in to comment.