'Page is not rendering after using `history.push` in Ionic/React with `IonTabs` as nested routes
I have an Ionic/React v5.0.7 app with IonReactRouter
to manage routing:
// App.tsx
return (
<IonApp>
<IonReactRouter history={history}>
<IonRouterOutlet>
<Route exact path={ROUTES.HOME} component={Home} />
<Route exact path={ROUTES.LOGIN} component={Login} />
<Route path={ROUTES.CONTRACTOR} component={ContractorDashboard} />
</IonRouterOutlet>
</IonReactRouter>
</IonApp>
)
In one of my routes (path={ROUTES.CONTRACTOR}
) I'm trying to implement IonTabs
which needs to have its own IonRouterOutlet
, so I have the tabs as nested routes like so:
// ContractorDashboard.tsx
<IonContent>
<IonTabs>
<IonTabBar slot="top">
<IonTabButton href="/contractor/overview" tab="contractor-overview">
<IonLabel>{t('ContractorDashboard.contractorOverview')}</IonLabel>
</IonTabButton>
<IonTabButton href="/contractor/contractor-configs" tab="contractor-configs">
<IonLabel>{t('ContractorDashboard.contractorConfig')}</IonLabel>
</IonTabButton>
<IonTabButton href="/contractor/workers-management" tab="workers-management">
<IonLabel>{t('ContractorDashboard.workersManagement')}</IonLabel>
</IonTabButton>
<IonTabButton href="/contractor/email-responses" tab="email-responses">
<IonLabel>Some label</IonLabel>
</IonTabButton>
</IonTabBar>
<IonRouterOutlet>
<Route path="/contractor/contractor-configs" render={() => <ContractorConfigs userId={authUser.uid} />} />
<Route path="/contractor/workers-management" component={WorkersManagement} />
<Route path="/contractor/email-responses" render={() => <EmailResponses userId={authUser.uid} />} />
<Route path="/contractor/contractor-overview" render={() => <IonTitle>Title</IonTitle>} />
<Route exact path="/contractor" render={() => <Redirect to="/contractor/overview" />} />
</IonRouterOutlet>
</IonTabs>
</IonContent>
Everything works fine until I try to create dynamic navigation to /contractor
route, which is the root of the tabs' router, using history.push('/contractor')
or <Redirect to="/contractor" />
.
In my login component I want to redirect automatically to the ContractorDashboard
if a user is authenticated. I.e. after a successful login the user should be redirected to the dashboard. I implemented the following:
// Login.tsx
useEffect(() => {
if (authUser) {
history.push('/contractor')
}
}, [authUser, history])
What happens is the URL is changed in the address bar but the login page is still visible instead of the ContractorDashboard
. I also tried this:
// Login.tsx
if (authUser) {
return <Redirect to="/contractor"/>
}
return (
// ...
)
The result is the same. I can see the contractor page in the dev tools but it's not rendered.
A few notes though:
- The result is a bit different when I try different things, like pushing to
/contractor/overview
insted of just/contractor
. Then the contractor dashboard is visible for a sec and then it's the login page again. - Sometimes I do get the dashboard, but the tabs are not visible. Its in the DOM and the cursor events are there but still not visible.
- Everything was working before using
IonTabs
in the dashboard. - What's weird is that when I try to navigate to the login page via the address bar with an authenticated user I'm redirected to the dashboard like I should and everything is working.
I did try replacing IonReactRouter
with Router
from react-router
like suggested in this answer. Then redirecting from the login page works but the tabs trigger a full page refresh on every tab change, which is just awful UX.
Here is my ionic info
output:
Ionic:
Ionic CLI : 6.11.0
Utility:
cordova-res : not installed
native-run : not installed
System:
NodeJS : v12.18.3
npm : 6.14.6
OS : Windows 10
and here's my package.json
:
{
"name": "application",
"version": "0.0.1",
"private": true,
"dependencies": {
"@capacitor/android": "^2.4.6",
"@capacitor/core": "2.4.6",
"@ionic/react": "^5.0.7",
"@ionic/react-router": "^5.0.7",
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.4.0",
"@testing-library/user-event": "^8.0.3",
"@types/jest": "^24.0.25",
"@types/node": "^12.12.24",
"@types/react": "^16.9.17",
"@types/react-dom": "^16.9.4",
"@types/react-router": "^5.1.4",
"@types/react-router-dom": "^5.1.3",
"firebase": "^8.2.3",
"i18next": "^19.8.4",
"i18next-http-backend": "^1.0.22",
"ionicons": "^5.0.0",
"react": "^16.13.0",
"react-dom": "^16.13.0",
"react-i18next": "^11.8.5",
"react-redux": "^7.2.2",
"react-router": "^5.1.2",
"react-router-dom": "^5.1.2",
"react-scripts": "^4.0.1",
"redux": "^4.0.5",
"redux-localstorage-simple": "^2.3.1",
"redux-thunk": "^2.3.0",
"typescript": "3.8.3"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject",
"appium": "appium --chromedriver-executable C:\\Users\\Ori\\code\\chromedrivers\\chromedriver-83-0-4103.exe",
"e2e:android": "protractor e2e/android-e2e.conf.js",
"e2e:web": "protractor e2e/web-e2e.conf.js"
},
"eslintConfig": {
"extends": "react-app"
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": {
"@capacitor/cli": "2.4.6",
"@types/jasmine": "^3.6.3",
"@types/jasminewd2": "^2.0.8",
"@types/react-redux": "^7.1.15",
"@types/redux-logger": "^3.0.8",
"appium": "^1.20.2",
"jasmine": "^3.6.4",
"jasmine-spec-reporter": "^6.0.0",
"protractor": "^7.0.0",
"redux-logger": "^3.0.6",
"ts-node": "^9.1.1"
},
"description": "An Ionic project"
}
So is there a way to properly implement this and how?
Please let me know if any info or code is missing here.
Any help will be greatly appreciated!
Update
So for anyone who is interested, I gave up looking for a solution. Instead I implemented my own basic tabs, without routes (never understood why tabs require routing anyway...).
Disregarding styles it's very simple:
const [tab, setTab] = useState<number>(0);
const mapTabs = [
'contractorOverview',
'contractorConfigs',
'workersManagement',
'emailResponses'
]
const handleTabChange = () => {
switch (tab) {
case 0: return <h3>Overview</h3>;
case 1: return authUser && <ContractorConfigs userId={authUser.uid} />;
case 2: return <WorkersManagement />;
case 3: return authUser && <EmailResponses userId={authUser.uid} />;
default: return <h3>Overview</h3>;
}
}
return (
// ....
<IonContent>
<IonToolbar color="secondary">
<IonButtons slot="start">
{mapTabs.map((_tab, tabIdx) => (
<IonButton
key={tabIdx}
id={`${_tab}`}
onClick={() => setTab(tabIdx)}
disabled={mapTabs[tab] === _tab}
>
{t(`ContractorDashboard.${_tab}`)}
</IonButton>
))}
</IonButtons>
</IonToolbar>
{handleTabChange()}
</IonContent>
// ....
)
And that's all you need to have a working tab view (again, without some styling it won't look so nice).
If someone has a solution to the tabs issue (actually I'm not 100% sure what caused the problem... I wasn't able to reproduce in stackblitz...) I would love to hear it.
If not I will post this as an answer...
Solution 1:[1]
Have you tried:
history.push({ pathname: '/contractor' })
or
history.push({ pathname: '/contractor/overview' });
Solution 2:[2]
did you try...
history.push('/contractor/overview')
can you post a small project on stackblitz or codesandbox?
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | Luís Silva |
Solution 2 | Aaron Saunders |