ionic-framework icon indicating copy to clipboard operation
ionic-framework copied to clipboard

bug: Nested IonRouterOutlet prevents component unmount

Open OoDeLally opened this issue 5 years ago • 30 comments

Bug Report

Ionic version:

5.0.1

Here is a repo with a minimal code to reproduce the bug: https://github.com/OoDeLally/ionic-nested-router-outlet-bug

Ionic info:

▶ ionic info 

Ionic:

   Ionic CLI       : 5.4.16 (/home/pascal/.nvm/versions/node/v8.16.0/lib/node_modules/ionic)
   Ionic Framework : @ionic/react 5.0.1

Utility:

   cordova-res                          : not installed
   native-run (update available: 0.3.0) : 0.2.9

System:

   NodeJS : v8.16.0 (/home/pascal/.nvm/versions/node/v8.16.0/bin/node)
   npm    : 6.4.1
   OS     : Linux 4.15


OoDeLally avatar Feb 24 '20 13:02 OoDeLally

Hi, any news of this? I've tried to investigate checking the source code, but it seems beyond my level of understanding.

OoDeLally avatar Mar 09 '20 14:03 OoDeLally

All of these issue links show the same problem that I am having trouble with. I replaced IonRouterOutlet with Switch, but IonTabs requires IonRouterOutlet, and I must delete IonTabs, and the app looks like ugly. Probably, the problem is caused by IonRouterOutlet. https://github.com/ionic-team/ionic/issues/20844 https://github.com/ionic-team/ionic/issues/20597 https://github.com/ionic-team/ionic/issues/20298 Duplicate of #20298 #20597 #20844

emindeniz99 avatar Mar 23 '20 22:03 emindeniz99

Anybody? This is a huge blocker for me

und3f1ned avatar Apr 06 '20 10:04 und3f1ned

For me too... So far the workaround we use is to force a complete page reload (window.location = '...'). But this is definitely ugly, as the whole page flickers and we lose the parent state.

OoDeLally avatar Apr 06 '20 10:04 OoDeLally

window.location

No, God, please, no :) Try this https://github.com/ionic-team/ionic/issues/20707#issuecomment-598806108

und3f1ned avatar Apr 06 '20 10:04 und3f1ned

window.location

No, God, please, no :) Try this #20707 (comment)

It doesn't work for us. When using <Switch>, the parent component successfully unmounts, but it also unmounts when it should not (when the path changes but should still keep the parent component mounted), leading to a blank page, without any kind of console error.

Staying with window.location = '...' for now, regarless of how ugly it is.

OoDeLally avatar Apr 07 '20 07:04 OoDeLally

window.location

No, God, please, no :) Try this #20707 (comment)

It doesn't work for us. When using <Switch>, the parent component successfully unmounts, but it also unmounts when it should not (when the path changes but should still keep the parent component mounted), leading to a blank page, without any kind of console error.

Staying with window.location = '...' for now, regarless of how ugly it is.

Did you try Ionic lifecycle methods? Especially this one useIonViewDidLeave(() => { console.log('ionViewDidLeave event fired'); });

und3f1ned avatar Apr 07 '20 07:04 und3f1ned

Did you try Ionic lifecycle methods? Especially this one useIonViewDidLeave(() => { console.log('ionViewDidLeave event fired'); });

I just did. None of the 4 events in https://ionicframework.com/docs/react/lifecycle was fired.

OoDeLally avatar Apr 07 '20 08:04 OoDeLally

I'm also experiencing the same issue, no lifecyle method will be fired. Tried both class and functional component using the ionic docs.

royvangeel avatar Apr 14 '20 06:04 royvangeel

Hello all,

I'm looking into this now, hope to have something to report on soon.

elylucas avatar Apr 14 '20 14:04 elylucas

Hi All,

We have a dev release that provides better support for nested IonRouterOutlet components. There is a new prop on IonRouterOutlet that lets the router know it is a nested outlet to better perform the transition.

If your'e outlet is a nested outlet directly rendered by a Route in a parent outlet, then use the ionPage prop on the IonRouterOutlet. Here is an example:

App.tsx:

const App: React.FC = () => (
  <IonRouterOutlet>
    <Route path="/sub1" component={Sub1Outlet}  />
    <Route path="/sub2" component={Sub2Outlet} />
  </IonRouterOutlet>
);

Sub1Outlet.tsx:

const Sub1Outlet: React.FC = () =>  (
    <IonRouterOutlet ionPage>
      <Route path="/sub1" exact={true} 
        render={() => <Redirect to="/sub1/page" />} />
      <Route path="/sub1/page" component={Page1} exact={true} />
    </IonRouterOutlet>
  );

Sub2Outlet.tsx:

const Sub2Outlet: React.FC = () =>  (
    <IonRouterOutlet ionPage>
      <Route path="/sub2" exact={true} 
        render={() => <Redirect to="/sub2/page" />} />
      <Route path="/sub2/page" component={Page2} exact={true} />
    </IonRouterOutlet>
  );

If you can, could you try to install it and let us know if it fixes the issue and if you run into any others? To install it run:

npm i @ionic/[email protected] @ionic/[email protected]

If all goes good this should be available in the next Ionic release.

Thanks!

elylucas avatar Jun 23 '20 22:06 elylucas

It didn't unmount the component for me. For now I will stick with routerDirection="back". Although it's not correct, it's working the way I need

mateuskb avatar Aug 04 '20 19:08 mateuskb

The issue continues. There is no need for nested ionrouteroutlet. The problem occurs at only one router. https://github.com/ionic-team/ionic-framework/issues/20298 https://github.com/ionic-team/ionic/issues/20844 https://github.com/ionic-team/ionic/issues/20597

emindeniz99 avatar Aug 05 '20 06:08 emindeniz99

@elylucas thanks for the fix! I'm noticing that going "back" from nested routes seems to have no transition animation when going back from a "level-3" nested route to a "level-2" nested route.

jasonogasian avatar Aug 06 '20 21:08 jasonogasian

I first noticed this issue with a single layer of routes (no nesting) and an apollo query with the no-cache option. Desired outcome, anytime i navigate to page X it would re-query the backend. I was surprised to see it not making the query a 2nd or more times. After days of searching git issues and changing versions of apollo/ionic etc i tried a useEffect callback function and noticed it wasn't working which is what apollo's useQuery uses to refetch.

All that to say i think the not unmounting components on forward and none directions is a premature optimization. What if we made this an option on the Route? <Route cache={true}/> or something? This way the dev has to opt into the optimization and isn't surprised by it. [If enough ppl +1s this idea i can start working on a PR]

basicBrogrammer avatar Aug 28 '20 11:08 basicBrogrammer

It didn't unmount the component for me. For now I will stick with routerDirection="back". Although it's not correct, it's working the way I need

What did you apply routerDirection="back" on?

patelrikin avatar Aug 29 '20 18:08 patelrikin

Hello @basicBrogrammer, I started working on a react app, currently it does not deviate much from the default one generated by ionic start with a side menu. It does not have nested routing. I have pages on different routes and I noticed the components do not unmount when I navigate from one route to the other, which for my use case is not good, as I want to refresh the content that I show when the user selects a view from the menu.

I could work it around by replacing IonRouterOutlet with a Switch, and wrapping it an IonContent to give it an id, but I can not judge what kind of side effects it would have. I think that the existing apis should give the developer control on when the component under a route is persisted or not, once the user leaves the route.

I think this is similar to what you described on https://github.com/ionic-team/ionic-framework/issues/20597#issuecomment-682473789, if I understood it well.

dellagustin avatar Oct 05 '20 18:10 dellagustin

Hi All,

We have a dev release that provides better support for nested IonRouterOutlet components. There is a new prop on IonRouterOutlet that lets the router know it is a nested outlet to better perform the transition.

If your'e outlet is a nested outlet directly rendered by a Route in a parent outlet, then use the ionPage prop on the IonRouterOutlet. Here is an example:

App.tsx:

const App: React.FC = () => (
  <IonRouterOutlet>
    <Route path="/sub1" component={Sub1Outlet}  />
    <Route path="/sub2" component={Sub2Outlet} />
  </IonRouterOutlet>
);

Sub1Outlet.tsx:

const Sub1Outlet: React.FC = () =>  (
    <IonRouterOutlet ionPage>
      <Route path="/sub1" exact={true} 
        render={() => <Redirect to="/sub1/page" />} />
      <Route path="/sub1/page" component={Page1} exact={true} />
    </IonRouterOutlet>
  );

Sub2Outlet.tsx:

const Sub2Outlet: React.FC = () =>  (
    <IonRouterOutlet ionPage>
      <Route path="/sub2" exact={true} 
        render={() => <Redirect to="/sub2/page" />} />
      <Route path="/sub2/page" component={Page2} exact={true} />
    </IonRouterOutlet>
  );

If you can, could you try to install it and let us know if it fixes the issue and if you run into any others? To install it run:

npm i @ionic/[email protected] @ionic/[email protected]

If all goes good this should be available in the next Ionic release.

Thanks!

This seemed like a promising take but it didn't work for me. I assume it's because I have the IonRouterOutlet wrapped in a Suspense component. I have resorted to putting up a 404 page. I feel this is much better than using a Switch which makes the app lose the lifecycle events and caching, and also the routing animations. I hope we will have that Redirect working in IonRouterOutlet soon.

allanyego avatar Oct 28 '20 13:10 allanyego

Hello @basicBrogrammer, I started working on a react app, currently it does not deviate much from the default one generated by ionic start with a side menu. It does not have nested routing. I have pages on different routes and I noticed the components do not unmount when I navigate from one route to the other, which for my use case is not good, as I want to refresh the content that I show when the user selects a view from the menu.

I could work it around by replacing IonRouterOutlet with a Switch, and wrapping it an IonContent to give it an id, but I can not judge what kind of side effects it would have. I think that the existing apis should give the developer control on when the component under a route is persisted or not, once the user leaves the route.

I think this is similar to what you described on #20597 (comment), if I understood it well.

I think if you replace IonRouterOutlet with a switch you won't get the ION transition animations. You can change the direction on the given route to "back" and it will reload .... or at least it did last time i played with it.

basicBrogrammer avatar Nov 10 '20 18:11 basicBrogrammer

routerDirection

I added routerDirection back to any link going to the given route that i wanted to "remount"

basicBrogrammer avatar Nov 10 '20 18:11 basicBrogrammer

what is the state on this issue? Nested routes result in breaking third party libraries (replacing ionRouterOutlet with Switch works) anyways navigation is crucial

wibed avatar Dec 08 '20 15:12 wibed

what is the state on this issue? Nested routes result in breaking third party libraries (replacing ionRouterOutlet with Switch works) anyways navigation is crucial

You have to remember that replacing IonRouterOutlet with a Switch will mean converting all your useIonViewDidEnter with useEffects and lose animations.

allanyego avatar Dec 08 '20 15:12 allanyego

Is there any new news?

emindeniz99 avatar Jan 07 '21 06:01 emindeniz99

I found a workaround.

You can set an id to the <IonPage>. When you click the header button, the app will go back and remove your page from the DOM.

This is to force unmount the page, and because this page is no longer on the DOM it cannot execute its life cycles.

This is my helper function

export const removePageFromDOM = (pageId: string) => {
    const element = document.getElementById(pageId)
    setTimeout(()=>{
        return element && element.remove()
    }, 1000) // Give a little time to properly unmount your previous page before removing the old one
}

This is my page

   return (
        <IonPage id="PageId">
              ...
        </IonPage>
    );

This is my custom header

const Header: React.FC<Props> = ({title}) => {
    const history = useHistory();

    const handleBack = ()=>{
        history.goBack();
        removePageFromDOM("PageId")
    }

    return (
        <IonHeader>
            <IonToolbar>
                    <IonButtons slot="start">
                        <IonButton onClick={handleBack}>
                            <IonIcon slot="icon-only" ios={chevronBack} md={arrowBack}/>
                        </IonButton>
                    </IonButtons>
                <IonTitle>{title}</IonTitle>
            </IonToolbar>
        </IonHeader>
    );
};

Hope this helps

MiguelHernandezCh avatar Jan 24 '21 02:01 MiguelHernandezCh

This is a huge blocker for me.

jamt0 avatar Feb 24 '21 15:02 jamt0

Any updates?

ItayElgazar avatar Sep 19 '21 17:09 ItayElgazar

It seems to me that Ionic just don't deal with these issues.

ashleydavis avatar Jan 21 '22 07:01 ashleydavis

In case of Vue framework, adding key attribute to "ion-router-outlet" resolved it for me

<ion-router-outlet id="main-content" :key="route.fullPath" ></ion-router-outlet>

e-kotsiuk avatar Aug 16 '22 13:08 e-kotsiuk

I found a workaround. I'm just using the React router feature to go to another page.

import { useHistory } from 'react-router-dom';
let history = useHistory();

//in your link or button
history.replace('your page');

That code triggers an unmounted event.

I hope this workaround is helpful to you.

Mati-Educa avatar Mar 27 '23 20:03 Mati-Educa

Stumbled across this issue today, found that links and ion items accept a routerOptions property with unmount: true that seems to take care of it.

jeffvandyke avatar Mar 02 '24 20:03 jeffvandyke