'React, navigate to react-bootstrap tab with history
I'm writing a multi-form page (login/registration/lost-password) using tabs (react-bootstrap).
react 17.0.2, react-bootstrap 2.2.1, react-router-dom 6.2.2
I try to implement the tab selection with url/history. I used this :
route :
const routes = [
{
path: '/',
childRoutes: [
{ path: '/', component: <HomePage />, isIndex: true },
{ path: '/user/:active_tab', component: <LoginPage /> },
],
},
];
history :
import { createBrowserHistory } from 'history';
const history = createBrowserHistory();
export default history;
LoginPage :
import React, { useState, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import { Tabs, Tab } from 'react-bootstrap';
import { useParams } from 'react-router-dom';
import { makeStyles } from '@material-ui/core/styles';
import styles from './styles';
import LoginForm from './LoginForm';
import history from '../../../common/history';
const useStyles = makeStyles(styles);
export default function LoginPage() {
const classes = useStyles();
const { t } = useTranslation();
const { active_tab } = useParams();
//let [ activeTab, setActiveTab ] = useState(active_tab);
const DEFAULT_INITIAL_TAB = 'login';
useEffect(() => {
if (!active_tab) {
history.push(`/user/${DEFAULT_INITIAL_TAB}`);
}
}, [])
const toggle = tab => {
if (active_tab != tab) {
history.push(`/user/${tab}`);
}
//setActiveTab(tab);
}
return (
<div className="container">
<div className="card mt-3" style={{ width:"550px" }}>
<div className="card-body">
<Tabs
id="login-page-tabs"
activeKey={active_tab} //{activeTab}
onSelect={toggle}
className="mb-3">
<Tab eventKey="login" title={t('Login-Title')}>
<LoginForm />
</Tab>
<Tab eventKey="register" title={t('Register-Title')}>
REGISTER
</Tab>
<Tab eventKey="lostpassword" title={t('LostPassword-Title')}>
LOST PASSWORD
</Tab>
</Tabs>
</div>
</div>
</div>
);
}
My problem is the following : When using "as is", clicking on a tab won't display the correct tab. Only <Link to={"user/login"} /> (in header for example) is working to display the correct tab.
When removing comments on
let [activeTab, setActiveTab] = useState();
setActiveTab(tab); // in toggle function
and replacing {active_tab} by {activeTab} in Tabs properties, the behaviors is inverted... Clicking on tabs show the correct display, but Link in header won't work...
history always work by replacing old tab key by new tab key (login, register, lostpassword).
I don't understand how to let all working together... I'm new to react, maybe the way I try to take is not correct ? If anyone has a suggestions...
Thanks in advance.
Solution 1:[1]
While you've created and exported a custom history object:
import { createBrowserHistory } from 'history';
const history = createBrowserHistory();
export default history;
This isn't the same history object instance that the BrowserRouter component instantiates and uses itself:
/** * A `<Router>` for use in web browsers. Provides the cleanest URLs. */ export function BrowserRouter({ basename, children, window, }: BrowserRouterProps) { let historyRef = React.useRef<BrowserHistory>(); if (historyRef.current == null) { historyRef.current = createBrowserHistory({ window }); // <-- the history object the Router uses! } let history = historyRef.current; let [state, setState] = React.useState({ action: history.action, location: history.location, }); React.useLayoutEffect(() => history.listen(setState), [history]); return ( <Router basename={basename} children={children} location={state.location} navigationType={state.action} navigator={history} /> ); }
When you import your history object and issue imperative navigation actions it will update the URL, as expected, but because it isn't the history context your router is using it isn't aware of any of the navigation actions occurring.
The solution is rather simple/trivial, import and use the useNavigate hook which replaced the react-router-dom@5 useHistory hook and exposes a navigate function.
Example:
import { useParams, useNavigate } from 'react-router-dom';
const DEFAULT_INITIAL_TAB = "login";
export default function LoginPage() {
const { t } = useTranslation();
const navigate = useNavigate(); // <-- access the navigate function
const { active_tab } = useParams();
const [activeTab, setActiveTab] = useState(active_tab);
useEffect(() => {
if (!active_tab) {
navigate(`/user/${DEFAULT_INITIAL_TAB}`); // <-- PUSH action
}
setActiveTab(active_tab);
}, [active_tab, navigate]);
//console.log('initial tab : ' + init);
const toggle = (tab) => {
if (active_tab !== tab) {
navigate(`/user/${tab}`); // <-- PUSH action
}
setActiveTab(tab);
};
return (
<div className="container">
<div className="card mt-3" style={{ width: "550px" }}>
<div className="card-body">
<Tabs
id="login-page-tabs"
activeKey={activeTab}
onSelect={toggle}
className="mb-3"
>
<Tab eventKey="login" title={t("Login-Title")}>
<LoginForm />
</Tab>
<Tab eventKey="register" title={t("Register-Title")}>
REGISTER
</Tab>
<Tab eventKey="lostpassword" title={t("LostPassword-Title")}>
LOST PASSWORD
</Tab>
</Tabs>
</div>
</div>
</div>
);
}
If you don't want to "pollute" the history stack with a bunch of entries navigating between tabs you can use a redirect (REPLACE) instead of PUSH actions.
Example:
navigate(`/user/${tab}`, { replace: true });
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 | Drew Reese |
