Hi there.
I’m trying to do some cleanup events when leaving ionic views but neither method is firing on any page of my app. I thought it may have been due to how the routing is declared, but I can’t find any solution.
<IonApp>
<IonReactRouter>
<IonRouterOutlet>
<Route path="/login" render={() => user ? <Redirect to="/home/pending-chats"/> : <Login/>} exact={true}/>
<Route path="/pending-chats/:contactId" component={LinkChat} />
<Route path="/conversations/:contactUserSwitchboardId" component={Chat} />
<Route path="/contacts/:contactId" component={ContactDetails} />
</IonRouterOutlet>
<Route path="/home" >
<IonTabs>
<IonRouterOutlet>
<Route path="/home/pending-chats" component={PendingChats} exact={true} />
<Route path="/home/conversations" component={Conversations} exact={true} />
<Route path="/home/contacts" component={Contacts} exact={true} />
<Route path="/home/configuration" component={Configuration} exact={true} />
</IonRouterOutlet>
<IonTabBar slot="bottom" style={{borderTop: 0}}>
<IonTabButton tab="pending-chats" href="/home/pending-chats">
{app.unreadPendingChats > 0 &&
<IonBadge className="notification-tabs" color="primary">
{app.unreadPendingChats < 100 ? app.unreadPendingChats : '99+'}
</IonBadge>
}
<IonIcon icon={peopleCircle} />
<IonLabel>{t('pendingChats.navTitle')}</IonLabel>
<IonRippleEffect />
</IonTabButton>
<IonTabButton tab="conversations" href="/home/conversations">
{app.unreadConversations > 0 &&
<IonBadge className="notification-tabs" color="primary">
{app.unreadConversations < 100 ? app.unreadConversations : '99+'}
</IonBadge>
}
<IonIcon icon={chatbubblesOutline} />
<IonLabel>{t('conversations.navTitle')}</IonLabel>
<IonRippleEffect />
</IonTabButton>
<IonTabButton tab="contacts" href="/home/contacts">
<IonIcon icon={personCircleOutline} />
<IonLabel>{t('contacts.navTitle')}</IonLabel>
<IonRippleEffect />
</IonTabButton>
<IonTabButton tab="configuration" href="/home/configuration">
<IonIcon icon={options} />
<IonLabel>{t('configuration.navTitle')}</IonLabel>
<IonRippleEffect />
</IonTabButton>
</IonTabBar>
</IonTabs>
</Route>
<Route render={() => user ? <Redirect to="/home/pending-chats" /> : <Redirect to="/login" />} />
</IonReactRouter>
</IonApp>
You didn’t show the content of any of your components, but I’m pretty sure that to get the transitions to work, the direct child of each <Route>
needs to be an <IonPage>
, so make sure that <LinkChat>
, <Chat>
, <ContactDetails>
and so on are wrapped in <IonPage>
.
Sure, just as an example, this is the login page:
const Login = () => {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [loading, setLoading] = useState(false);
const [errors, setErrors] = useState({
login: false,
server: false
});
useIonViewDidEnter(() => {
unregister();
}, []);
const {update} = useContext(AppContext);
const router = useIonRouter();
const loginSubmit = (e) => {
e.preventDefault();
setLoading(true);
setErrors({
login: false,
server: false
});
AuthService.login(email, password).then(async () => {
checkNotifications();
const user = await AuthService.getLoggedUser();
update({user});
router.push("/home/pending-chats");
},
(error) => {
if (error?.response?.data?.errorCode === 12011) {
setErrors({...errors, login: true});
} else {
setErrors({...errors, server: true});
}
}
).finally(() => {
setLoading(false);
});
}
useIonViewWillEnter(async () => {
if (await AuthService.getCurrentUser()) {
router.push("/home/pending-chats");
}
});
useIonViewWillLeave(() => {
console.log("Login will leave");
}, []); // tried to use it without the dependency array too
return (
<IonPage>
<IonLoading isOpen={loading} />
<IonContent>
<IonGrid style={{height: "100vh"}}>
<IonRow className="ion-align-items-center" style={{height: "100vh"}}>
<IonCol size="12">
<form onSubmit={loginSubmit} style={{marginTop: "auto", marginBottom: "auto"}}>
<IonRow className="ion-justify-content-center">
<IonCol size="12" className="ion-margin-bottom ion-margin-top ion-text-center">
<img src="/assets/img/logo.png" alt="logo"></img>
</IonCol>
<IonCol size="10" className="ion-margin-top">
<IonItem>
<IonLabel position="stacked" color="medium" className="ion-text-center">{t('login.email')}</IonLabel>
<IonInput type="email" className="ion-text-center" value={email} onIonChange={(e) => setEmail(e.target.value)} required />
</IonItem>
</IonCol>
<IonCol size="10">
<IonItem>
<IonLabel position="stacked" color="medium" className="ion-text-center">{t('login.password')}</IonLabel>
<IonInput type="password" className="ion-text-center" value={password} onIonChange={(e) => setPassword(e.target.value)} required />
</IonItem>
</IonCol>
<IonCol size="12" className="ion-text-center">
<IonButton fill="outline" mode="ios" type="submit" className="ion-margin-top">
{t('login.button')}
</IonButton>
</IonCol>
<IonCol size="12">
<Alert icon={alertCircleOutline} isOpen={errors.server} message={t("login.error.server")} color="white" textColor="danger" />
<Alert icon={alertCircleOutline} isOpen={errors.login} message={t("login.error.initNoLogin")} color="white" textColor="danger" />
</IonCol>
</IonRow>
</form>
</IonCol>
</IonRow>
</IonGrid>
</IonContent>
<IonFooter>
</IonFooter>
</IonPage>
);
}
export default Login;
As I said, I tried using useIonViewDidLeave, rearranging the app routes, etc. and I do not know what to do anymore
I am not an expert on the routing system, but the problem might be your use of router.push()
. The Ionic Docs (React Navigation: Router Link Redirect to Navigate to Another Page) recommend using history.push
.
Still not getting to trigger the hook, so definitely not the reason it’s not working. Am I missing something or is this kind of behaviour unusual?
The hooks should be triggered if your routes are defined correctly-- I have an app with several hundred routes, including manually defined routes, dynamically defined routes, and nested routes, and I have not had trouble getting IonViewDidEnter/Leave to trigger.
Looking at your routes, one thing I would try is to disable your <Route path="/home">
route section and see if you can get the hooks to work.
The issue with the <Route path="/home">
section is that the home route is not wrapped in <IonRouterOutlet>
, which is what is managing the transitions between IonPages.
So the structure should be something like:
<IonRouterOutlet>
<Route path="/login" render={() => user ? <Redirect to="/home/pending-chats"/> : <Login/>} exact={true}/>
<Route path="/pending-chats/:contactId" component={LinkChat} />
<Route path="/conversations/:contactUserSwitchboardId" component={Chat} />
<Route path="/contacts/:contactId" component={ContactDetails} />
<Route path="/home" component={routerOutletHome} />
</IonRouterOutlet>
And then component routerOutletHome
:
<IonPage>
<IonRouterOutlet>
<Route path="/home/pending-chats" component={PendingChats} exact={true} />
<Route path="/home/conversations" component={Conversations} exact={true} />
<Route path="/home/contacts" component={Contacts} exact={true} />
<Route path="/home/configuration" component={Configuration} exact={true} />
</IonRouterOutlet>
</IonPage>
Note that the nested router outlet is wrapped in <IonPage>
. This is necessary to prevent the IonRouterOutlets from fighting each other.
Anyway, nested routes have several tricky points, so first I would remove the route nesting from your app and test the hooks that way.
The problem of wrapping the outlet with <IonPage>
is that it throws an error
Uncaught Error: IonTabs must contain an IonRouterOutlet
I suppose <IonTabs>
expects a <IonRouterOutlet>
as a direct child. And if I wrap the whole <IonTabs>
, tabs are showing outside the tab pages and the page stops working
What I’ve noticed is that all the solutions (that I’ve seen) that allows me to have routes without tabs, disable those hooks because it’s probably confused about the routing. Would love if there was some “official way” of having both tab and tabless routes
You can have both tabbed and tabless routes.
To do so, one way is to put the routes that should have tabs in their own component with <IonTabs>
, the router for those tabs, and everything else as described here.
Just forked the repo you sent to reproduce my issue and this is what I can show:
- When leaving a tabless component, neither hook will trigger, regardless if you enter a tab or tabless component
- When leaving a tab component, the hook only triggers if you enter another tab components (or if you “switch tabs”)
At this point I suppose this is a bug, unless there is something I’m not getting right
The tabless components aren’t in <IonRouterOutlet>
.
See this section of the docs. The routes must be wrapped in <IonRouterOutlet>
for the lifecycle methods to be available.
1 Like
This almost doest it, the only problem remaining is when switching from a tab view into a tabless one. Also I thought it was bad to wrap nested <IonRouterOutlet>
components, but as long as it does the trick I’m on it (since if you don’t, it seems the problem persists between tab and tabless, and you can’t remove the tab <IonRouterOutlet>
)
Still, this is quite useful to know, it just never ocurred to me to nest <IonRouterOutlet>
As I wrote earlier in the thread, you have to wrap the nested <IonRouterOutlet>
in <IonPage>
. You need to do this because <IonPage>
inside <IonRouterOutlet>
tells Ionic it needs to do a transition and fire the lifecycle events. If you do not do this, the events do not get fired.
So, in short, to get the lifecycle events to fire, you need two components: <IonRouterOutlet>
and <IonPage>
. Inside <IonRouterOutlet>
, each <IonPage>
will fire the lifecycle events.
But! If you have a nested <IonRouterOutlet>
, it needs to be wrapped in an <IonPage>
the same as any other component to get the lifecycle methods to fire.
Hello! I am still experiencing the issue @p-Jimenez described, with lifecycle functions not firing when moving from a tabbed view to a tabless one. Based on your last comment, @ptmkenny, are you saying the structure for the tabbed routes should look like this?
<IonPage>
<IonTabs>
<IonRouterOutlet>
<Route a />
<Route b />
</IonRouterOutlet>
</IonTabs>
</IonPage>
Because doing so results in a whitescreen error. At the same time, not wrapping the tabbed routes in an IonPage means that useIonViewDidLeave won’t work when moving from a tabbed to an untabbed route. What’s the best strategy here?
No, <IonTabs>
should not be wrapped in <IonPage>
.
In my comment above, I am saying "If you want to nest an <IonRouterOutlet>
inside another <IonRouterOutlet>
, you need to wrap the nested <IonRouterOutlet>
in <IonPage>
.
For <IonTabs>
, they should wrap the <IonRouterOutlet>
that they are supposed to be shown for.
You said you’re having trouble “moving from a tabbed to an untabbed route.” You’ll have to share more details of your configuration, but my guess is that the router is getting confused moving from tabs to the page where you somehow disabled the tab bar. See how tabs in ionic work.
One easy way to get rid of the tab bar is to use a modal.
Another quick-and-dirty way is to temporarily hide it with CSS.
Thanks for the quick response!
The “trouble” I mentioned is just that the lifecycle functions are not firing (useIonViewWillLeave, useIonViewDidLeave). There are workarounds to this, but I wanted to do things the “right” way.
On a deeper level, I think the issue is that the untabbed screens are only outside of the <IonTabs>
component in order to hide the tab bar. This can be fixed by switching to a modal or using CSS, as you mentioned. I’ll look into those options.
It seems that Ionic React Router is designed to either have routes with tabs, or to have routes with no tabs, but not to “mix routes with tabs and routes without tabs.” You can get it to work, but it’s tricky and can break in weird ways.
Here’s the thread with several ways to hide the tab bar in CSS.
Modals are described pretty well in the docs.
1 Like