Nested Routes in Ionic React with Tabs

Hi every body,

I have been developing PWA application which has tabs (6 tabs) and 2 of tabs are only for users that logged in. I am trying to show login/signup page when they head over those 2 tabs. My login/signup components wrapped around <IonPage> and when every thing seems working fine but when I try to navigate to a nested route inside a tab (like Profile) when I press back button, everything breaks and I see other Tab content instead of Profile tab content. If I refresh the browser, everything works as expected but after login, everything breaks

Questions:

  1. what am I suppose to do if some part of application is public and some part is private ? how should I set up my Routes and Tabs structure ?
  2. Where should I put login/Signup page route, Login/Signup is not part of any tab ?
  3. Do I need a specific route for login/signup ? or I should return a simple component ?

App Component

export default function App() {
	
	return (
		<IonApp>
			<IonReactRouter>
				<IonRouterOutlet>
					<Route path="/tabs" render={() => <Tabs />} />
					<Route exact path="/">
						<Redirect to="/tabs" />
					</Route>
				</IonRouterOutlet>
			</IonReactRouter>
		</IonApp>
	)
}

Tabs Component

export default function Tabs() {
	
	return (
		<IonPage>
			<IonContent>
				<IonTabs>
					<IonRouterOutlet>
						<Redirect exact path="/tabs" to="/tabs/home"/>
						<Route exact path={routes.home} component={HomeTab}/>
						<Route exact path={routes.shoppingCart} component={ShoppingCartTab}/>
						<Route exact path={routes.calendar} component={CalendarTab}/>
						<Route exact path={routes.myPrograms} component={MyProgramTab}/>
						<Route exact path={routes.etiquette} component={EtiquettesTab}/>
						<Route path={routes.profile} component={ProfileTab}/>
						
						
						<Route exact path={routes.myWorkoutProgram} component={MyWorkoutProgram}/>
						<Route exact path={routes.myDietPlan} component={MyDietPlan}/>
						<Route exact path={routes.myDailyWorkout} component={MyDailyWorkout}/>
						<Route exact path={routes.myDailyDietPlan} component={MyDailyDietPlan}/>
						
						<Route exact path={routes.confirmOtp} component={Otp}/>
						<Route exact path={routes.editUserInfo} component={EditUserInfo}/>
						<Route exact path={routes.editUserBodyInfo} component={UserBodyInfo}/>
						<Route exact path={routes.changePassword} component={PasswordChange}/>
						
						<Route exact path={routes.checkout} component={Checkout}/>
						<Route exact path={routes.subscriptionPlans} component={SubscriptionPlan}/>
						<Route exact path={routes.recommendation} component={Recommendation}/>
						<Route exact path={routes.complain} component={Complain}/>
						<Route exact path={routes.addWorkoutProgram} component={AddWorkoutProgram}/>
						<Route exact path={routes.addDietPlan} component={AddWorkoutProgram}/>
						<Route exact path={routes.workoutDays} component={WorkoutDays}/>
						<Route exact path={routes.addSets} component={AddSets}/>
						<Route exact path={routes.dietPlanDays} component={dietPlanDays}/>
						<Route exact path={routes.addMeals} component={AddMeals}/>
						<Route exact path={routes.notFound} component={NotFound}/>
						<Route exact path={routes.rules} component={GymRules}/>
						
						<Route exact path="/tabs">
							<Redirect to={routes.home}/>
						</Route>
						<Redirect exact path="/" to="/tabs/home"/>
						<Route component={NotFound}/>
					</IonRouterOutlet>
					
					<IonTabBar slot="bottom">
						<IonTabButton tab="profile" href={routes.profile}>
							<IonIcon aria-hidden="true" icon={personSharp} size='small'/>
							<IonLabel>Profile</IonLabel>
						</IonTabButton>
						<IonTabButton tab="etiquette" href={routes.etiquette}>
							<IonIcon aria-hidden="true" icon={readerSharp} size='small'/>
							<IonLabel>Etiquette</IonLabel>
						</IonTabButton>
						<IonTabButton tab="my-program" href={routes.myPrograms}>
							<IonIcon aria-hidden="true" icon={barbellSharp} size='small'/>
							<IonLabel>My Programs</IonLabel>
						</IonTabButton>
						<IonTabButton tab="calendar" href={routes.calendar}>
							<IonIcon aria-hidden="true" icon={calendarSharp} size='small'/>
							<IonLabel>Calendar</IonLabel>
						</IonTabButton>
						<IonTabButton tab="cart" href={routes.shoppingCart}>
							<IonIcon aria-hidden="true" icon={bagHandle} size='small'/>
							<IonLabel>shopping Cart</IonLabel>
						</IonTabButton>
						<IonTabButton tab="home" href={routes.home}>
							<IonIcon aria-hidden="true" icon={homeSharp} size='small'/>
							<IonLabel>Home</IonLabel>
						</IonTabButton>
					</IonTabBar>
				</IonTabs>
			</IonContent>
		</IonPage>
	)
}

Routes File

export const routes = {
	myDietPlan: "/tabs/my-programs/my-diet-plan",
	myWorkoutProgram: "/tabs/my-programs/my-workout-program",
	myDailyDietPlan: "/tabs/my-programs/my-daily-diet",
	myDailyWorkout: "/tabs/my-programs/my-daily-workout",
	
	default: "/",
	home: "/tabs/home",
	calendar: "/tabs/calendar",
	shoppingCart: "/tabs/shopping-cart",
	myPrograms: "/tabs/my-programs",
	etiquette: "/tabs/etiquette",
	profile: "/tabs/profile",
	
	editUserInfo: "/tabs/profile/edit-user-info",
	editUserBodyInfo: "/tabs/profile/edit-user-physical-info",
	logout: "/tabs/profile/logout",
	confirmOtp: "/tabs/profile/confirm-otp",
	changePassword: "/tabs/profile/change-password",
	checkout: "/tabs/shopping-cart/checkout",
	subscriptionPlans: "/tabs/home/subscription-plans",
	recommendation: "/tabs/profile/recommendation",
	complain: "/tabs/profile/complain",
	rules: "/tabs/profile/rules",
	addWorkoutProgram: "/tabs/profile/add-workout-program",
	addSets: "/tabs/profile/add-workout-program/add-sets",
	workoutDays: "/tabs/profile/add-workout-program/workout-days",
	addDietPlan: "/tabs/profile/add-diet-plan",
	addMeals: "/tabs/profile/add-diet-plan/add-meals",
	dietPlanDays: "/tabs/profile/add-diet-plan/diet-days",
	notFound: "/not-found",
}

Login Component

export default function Login() {
	
	const {register, handleSubmit, formState: {errors}} = useForm()
	const [login, {status, isLoading, error, data}] = useLoginMutation()
	
	const onSubmit = (data: any) => {
		login({
			phoneNumber: data.phoneNumber,
			password: data.password,
			deviceName: "iphone 13 pro max"
		})
	}
	
	if (isLoading) {
		return <Spinner/>
	}
	
	if (status === "rejected") {
		console.log(error)
		console.log("failed and you must show the error in the form")
	}
	
	return (
		<IonPage>
			<IonContent fullscreen>
				<div className='authWrapper'>
					<div className="signInContent">
						<h2 style={{color: "white"}}>Sign In</h2>
						<IonCard className="signInCard">
							<IonCardContent>
								<form onSubmit={handleSubmit(onSubmit)} className="signupCardForm">
									<IonGrid>
										<IonRow>
											<IonCol className="inputCol">
												<IonInput
													label="Phone Number"
													labelPlacement="floating"
													fill="outline"
													placeholder="Phone Number"
													{...register("phoneNumber", {required: true})}
												></IonInput>
											</IonCol>
										</IonRow>
										<IonRow>
											<IonCol>
												<IonInput
													label="password"
													labelPlacement="floating"
													fill="outline"
													placeholder="password"
													{...register("password", {required: true})}
												></IonInput>
											</IonCol>
										</IonRow>
									</IonGrid>
									<IonButton type="submit" expand="block" className="loginBtn">
										{isLoading ? <IonSpinner/> : 'ورود'}
									</IonButton>
								</form>
							</IonCardContent>
						</IonCard>
					</div>
				</div>
			</IonContent>
		</IonPage>
	)
}

here is an older project you can use as a reference to get you started.

ionic-sidemenu-tabs-auth-starter/LoginPage.tsx at master · aaronksaunders/ionic-sidemenu-tabs-auth-starter · GitHub

I am working on updating the project and the video, feel free to ping me if you have any questions

@aaronksaunders

Thanks for sharing the awesome project but In my scenario users can use part of the app so they don’t require to login first and that’s the problem here.

It is the same basic concept… I didn’t build your exact project for your but I was pointing you toward a solution to build upon

A simple way to do this is to create two tab bar components, one for logged-in users and one for non-logged-in users, and don’t show the tabs for logged-in users until the user actually logs in.

The reason is that each tab has its own navigation stack.

For login/signup, you could use a component wrapped in ion-modal, which will let you place it on multiple tabs. (If you give the form a route, it can only be associated with one tab.)

have you tried this? I am not sure you can dynamically swap tabBars in and out in an ionic application… also if it were me, @VeRJiL I would rethink the user experience. Would a user really expect the tabs to change based on their logged in state? You might be better off using a sidemenu and just add and remove menu items based on the logged in state.

Finally not sure the title reflects what the actual question is here, I am not certain what this has to do with nested routes

@aaronksaunders Thank you for sharing the project but I have already built the app and the only thing that breaks the app is routing system.

Thank you guys for your response. I have decided to change UX of the app.

  1. If user is not not logged in, I wont let him see the content of the app
  2. I use profile section like apple store which user can see the tabs and visit the app without login but I move 2 tabs which were only for logged in users to the profile section. this profile section will work on modal.

I have to choose one of these approaches.

one more question: how can I use nested routing in a modal?

Yes, I built an app that switches ion-tab-bars upon login. Unfortunately the app’s code is pretty complex so I don’t have a simple example, but I did build an app that does this.

one more question: how can I use nested routing in a modal?

Modals don’t have routes, so I’m not sure what you mean by using nested routes. Modal display on/off is usually controlled by state in Ionic React, not by the route.

Imagine if i move my profile tab content to a modal like app store does then I will have some thing like this open Profile modal -> click on edit information . In this scenario There should be back button after user enters edit information page on modal(which in app store there is a similar scenario!)

It would be nice if you could share your routing and tab bar component code.

ion-modal usually doesn’t have a back button. If you enter information and hit submit, why would there be a back button? Instead, the modal should close and the information should be updated. If there was an error, you can either catch it in form validation before the modal is closed to prevent the modal from closing, or you can use ion-alert to show the error.

The app I built is pretty complex and I don’t have a ready code sample to share.