Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(react): nested router outlets will not reanimate entered views #24672

Merged
merged 4 commits into from
Jan 31, 2022

Conversation

sean-perkins
Copy link
Contributor

@sean-perkins sean-perkins commented Jan 28, 2022

Pull request checklist

Please check if your PR fulfills the following requirements:

  • Tests for the changes have been added (for bug fixes / features)
  • Docs have been reviewed and added / updated if needed (for bug fixes / features)
  • Build (npm run build) was run locally and any changes were pushed
  • Lint (npm run lint) has passed locally and any fixes were made for failures

Pull request type

Please check the type of change your PR introduces:

  • Bugfix
  • Feature
  • Code style update (formatting, renaming)
  • Refactoring (no functional changes, no api changes)
  • Build related changes
  • Documentation content changes
  • Other (please describe):

What is the current behavior?

When navigating within a nested router outlet, the page containing the router outlet is transitioning when the view is already visible. The most common instance of this, is when using nested router outlets in combination with IonTabs. When navigating between the tabs, the page containing IonTabs is transitioning, resulting in a flickering display.

Issue Number: #24107

What is the new behavior?

The logic for determining when a page transition should occur has been amended to account for the conditions that apply when you are either within a single router outlet or within a nested router outlet.

Navigating between tabs within a nested router outlet will no longer flicker, as all subsequent routing will not transition the page containing the router outlet.

For example, when navigating between /tabs/tab1 to /tabs/tab2, only the view elements for tab1 and tab2 will transition. The view element containing the /tabs path and router outlet will not transition, as that view is already active in the viewport.

Does this introduce a breaking change?

  • Yes
  • No

Other information

This change should have a noticeable performance improvement for React applications using nested router outlets. Prior to this PR, the page element was transitioning and causing multiple repaints, when the view was already active. This likely wasn't caught prior to the custom elements build, as Stencil's hydrated (.hydrated) class was overriding the styling from ion-page-invisible.

Kapture.2022-01-28.at.14.10.44.mp4

@sean-perkins sean-perkins requested a review from a team as a code owner January 28, 2022 05:18
@github-actions github-actions bot added the package: react @ionic/react package label Jan 28, 2022
@sean-perkins
Copy link
Contributor Author

sean-perkins commented Jan 28, 2022

Looks like I need to address some issues with regards to failing tests. Need to validate that the criteria they are validating is accurate to expected behavior vs. testing the previous incorrect behavior.

I also somehow completely missed that the react router package also has its own test app. I can likely remove all the custom changes to the react test app and just validate changes there.

@sean-perkins
Copy link
Contributor Author

sean-perkins commented Jan 28, 2022

Ok, only one outstanding test is failing, which I will address in the morning.

[1] 2) Nested Outlets 2
[1] /nested-outlet2 >
[1] Go to list from Home IonItem click >
[1] Item#1 IonItem Click >
[1] Item page should be visible >
[1] Back >
[1] Home page should be visible

I believe this test is covering incorrect behavior. Now when clicking the Back > interaction, you will be transitioned to the list view that you clicked Item #1 IonItem Click > from, instead of going back two views to the Home page 🎉

@sean-perkins
Copy link
Contributor Author

Verified in the reproduction app (from the attached issue) and the Ionic React conference app.

@sean-perkins
Copy link
Contributor Author

sean-perkins commented Jan 28, 2022

Performance tests comparing v6.0.4 against this PR.

Navigating between tabs in a nested outlet (navigating from /tab-1, to /tab-2 and back to /tab-1)

v6.0.4 PR Device/Env
M1 Macbook Air 16gb ram / Chrome
iPhone XS Max iOS 15.2.1 / Mobile Safari

Noticeable improvements:

  • 66% decrease in render time in chrome browser
  • 51% decrease in render time in mobile Safari

Copy link
Contributor

@averyjohnston averyjohnston left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome work, and thanks again for the live walkthrough! 🎉

Copy link
Contributor

@liamdebeasi liamdebeasi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great job! One non-blocking comment.

Comment on lines +115 to +127
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;
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it make sense to only check if enteringViewItem.routeData.match.url !== routeInfo.pathname?

If enteringViewItem === leavingViewItem but enteringViewItem.routeData.match.url === routeInfo.pathname then that would mean you went from /page/1 to /page/1 which I don't think is possible.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This specific check is to catch the difference in behavior between changing between tabs vs. pushing to another route that uses the same component.

With tabs and our reliance on React's re-rendering as a result of props changing to trigger route transition, the /tabs component will reach this check when navigating between /tabs/tab1 to /tabs/tab2. But, since the entering view item's rout date is /tabs and not /tabs/tab1, it will return early (skipping the unnecessary transition).

If pushing to the same detail page, the entering and leaving view item will be the same, but the entering view item's route will be /page/1 and the route info will be /page/1, so the transition will occur.

I'm a little worried about opening up this check without the entering/leaving comparison, since this is the only case that is currently attempting to transition when it shouldn't. Ideally all of this goes away, if we move off React's re-rendering/prop changes for handling route transitions and follow closer suite to the Vue implementation.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense, let's keep it as is 👍

@shadowzack
Copy link

I have tried using version 6.0.4 but still got the same problem

@sean-perkins
Copy link
Contributor Author

@shadowzack the problem still exists in v6.0.4. The comparison screenshots are comparing the incorrect behavior vs. the corrected behavior.

You need to upgrade to 6.0.5 or later: https://github.com/ionic-team/ionic-framework/blob/main/CHANGELOG.md#605-2022-02-02

@shadowzack
Copy link

I have just tried version 6.0.5 and the latest 6.0.13, all have the same problem

@sean-perkins
Copy link
Contributor Author

If you want to open an issue with a reproduction starter app, I would be happy to look.

@shadowzack
Copy link

I have tried to build a test app with the latest ionic core 6.0.13, and the app works as indented with no flickering.
can an inner IonRouterOutlet inside another IonRouterOutlet effect the transition?
i have both tabs and a side menu just like the ionic react conference app, but even after removing the side menu the flickering still exists.
any ideas on why this is happing?

@sean-perkins
Copy link
Contributor Author

If I recall, I verified this issue against the conference app and noticed an improvement to the transition between tab items (nested outlets). The only issue currently that I am aware of this is causing flicker transitions is fallback routes: #24855.

If you are able to pull in the similarities between your application and a new project, to isolate the reproduction, I can give a better recommendation. Nested outlets are unfortunately complex to handle page transitions and mounting/unmounting with how we replicate native page transitions.

@shadowzack
Copy link

shadowzack commented Mar 29, 2022

<IonApp>
    <IonReactRouter>
      <IonSplitPane contentId="main">
        <Menu />
      {/*   <IonRouterOutlet id="main"> */}
          <Route path="/" exact>
            <Redirect to="/login" />
          </Route>
          <Route path="/login" exact>
            <Login />
          </Route>
          <IonTabs>
            <IonRouterOutlet>
              <Route path="/notifications" exact>
                <TabNotifications />
              </Route>
              <Route path="/home" exact>
                <TabHome />
              </Route>
              <Route path="/chats" exact>
                <TabChats />
              </Route>
              <Route path="/operations" exact>
                <TabList />
              </Route>
              <Route path="/profile" exact>
                <TabProfile />
              </Route>
              <Route path="/support" exact>
                <TabSupport />
              </Route>
            </IonRouterOutlet>

            <IonTabBar slot="bottom">
              <IonTabButton tab="tabProfile" href="/profile">
                <IonIcon icon={personOutline} />
                <IonLabel>החשבון שלי</IonLabel>
              </IonTabButton>
              <IonTabButton tab="tabNotifications" href="/notifications">
                <IonIcon icon={notificationsOutline} />
                <IonLabel>התראות</IonLabel>
              </IonTabButton>
              <IonTabButton tab="tabHome" href="/home">
                <IonIcon icon={homeOutline} />
                <IonLabel>דף הבית</IonLabel>
              </IonTabButton>
              <IonTabButton tab="tabOperations" href="/operations">
                <IonIcon icon={listOutline} />
                <IonLabel>פעולות</IonLabel>
              </IonTabButton>
              <IonTabButton tab="tabChats" href="/chats">
                <IonIcon icon={chatbubbleOutline} />
                <IonLabel>הודעות</IonLabel>
              </IonTabButton>
            </IonTabBar>
          </IonTabs>
        {/* </IonRouterOutlet> */}
      </IonSplitPane>
    </IonReactRouter>
  </IonApp>

this is the code above, after running the code without the outer IonRouterOutlet the tabs transition without flickering. i think this might be related to the fact that the side menu and the tabs have the same paths(for example home using tabs and also using the side menu)

@shadowzack
Copy link

I have downloaded the ionic-react-conference-app, even without adding anything just running the code you can see the flickering when moving between tabs. can you try to fix it I think fixing it will help me and the anyone stuck in this problem.

@shadowzack
Copy link

shadowzack commented Mar 31, 2022

I have migrated back to ionic 5 until this issue is resolved, TIP - you can migrate back using this command:-
npm install @ionic/react@v5-lts @ionic/react-router@v5-lts

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
package: react @ionic/react package
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants