We have a sidebar that uses history.push to navigate from one page to another in a SPA. When this happens context providers are preserved but the pages useState goes back to its default value. I looked at the code for Link and its not doing anything different then the history push.
We can rewrite to use Link but I read that history push should work and that is all Link is doing anyway. The code below uses route render but have tried with just the JSX like others also.
<Router history={history}>
<div className={classes.body}>
<div className={classes.root}>
<Drawer appConfig={appConfig} />
<div style={{ width: '100%', height: '100vh', overflow: 'hidden' }}>
<Switch>
<Route type="public" path="/dialogs" exact render={Markets} />
<Route type="public" path="/notifications" exact>
<Notifications />
</Route>
<Route type="public" path="/:marketId" exact>
<Market />
</Route>
<Route type="public" path="/:marketId/about" exact>
<About />
</Route>
<Route>
<PageNotFound />
</Route>
</Switch>
</div>
</div>
</div>
</Router>
function Markets(props) {
const { intl } = props;
const { marketDetails, loading } = useAsyncMarketContext();
const [addMode, setAddMode] = useState(false);
function toggleAdd() {
setAddMode(!addMode);
}
function onMarketSave() {
toggleAdd();
}
return (
<Activity
title={intl.formatMessage({ id: 'sidebarNavDialogs' })}
isLoading={loading}
titleButtons={<IconButton onClick={toggleAdd}><AddIcon /></IconButton>}
>
<div>
{!addMode && <MarketsList markets={marketDetails} /> }
{addMode && <MarketAdd onCancel={toggleAdd} onSave={onMarketSave} />}
</div>
</Activity>
);
}
The expectation is that if addMode is true it will retain its value when clicking to /notifications and back.
Related
Hello Stackoverflow community
I am building a simple SPA using react. My navigation for the app would be a unauthenticated public page(like home/about/pricing etc) and a sign in button that the user will click on to be redirected into the app components after authentication using keycloak.
The way I have structured the app is having a parent router that will redirection between public facing files and then another router that will help router inside the app. The problem I am facing is my app router works but no HTML is displayed or no components are displayed (dashboard component not displayed)
My app.js file
import React from "react";
import { BrowserRouter, Route, Routes } from "react-router-dom";
import HomePage from "./pages/Homepage";
import AboutPage from "./pages/AboutPage";
import SecuredPage from "./pages/Securedpage";
import PricingPage from "./pages/PricingPage";
function App() {
return (
<div>
<BrowserRouter>
<Routes>
<Route exact path="/" element={<HomePage />} />
<Route path="/about" element={ <AboutPage />} />
<Route path="/pricing" element={ <PricingPage />} />
<Route exact path="/app" element={ <SecuredPage />} />
</Routes>
</BrowserRouter>
</div>
);
}
export default App;
My HomePage.JS contains
import React from 'react';
import NavMain from "../components/NavMain";
const Home = () => {
return (
<div>
<NavMain/>
<h1 className="text-green-800 text-4xl">Welcome to the Homepage. Some more text123</h1>
</div>
);
};
export default Home;
My NavMain.JS contains
<ul>
<li>
<a href="/">
Home
</a>
</li>
<li>
<a href="/about">
About
</a>
</li>
<a href="/pricing">
pricing
</a>
</li>
<li>
<a href="/app/dashboard">
app
</a>
</li>
</ul>
The public facing components work correctly. Once I click on 'app' I am redirected to the keycloak authentication page and from there after login I am on the securepage page component. But it does not render the dashboard component
SecuragePage.js
import React from 'react';
import { BrowserRouter, Route, Routes } from "react-router-dom";
import { ReactKeycloakProvider } from "#react-keycloak/web";
import keycloak from "../Keycloak";
import Dashboard from "./Dashboardpage";
import AboutPage from "./AboutPage";
import PrivateRoutes from "../helpers/PrivateRoutes";
import NavHomePage from "../components/NavHomePage";
const Loading = () => <div>Loading...</div>
const Secured = () => {
return (
<div>
<ReactKeycloakProvider authClient={keycloak}
initOptions={{
onLoad: "login-required",
}}
LoadingComponent={<Loading />} >
app landing page
<NavHomePage/>
<Routes>
<Route element={<PrivateRoutes />}>
<Route exact path="/app/dashboard" element={ <Dashboard />} />
</Route>
</Routes>
</ReactKeycloakProvider>
</div>
);
};
export default Secured;
Dashboard.js
import React from 'react';
const Dashboard = () => {
return (
<div>
<h1 className="text-green-800 text-4xl">Dashboard</h1>
</div>
);
};
export default Dashboard;
NavHomePage.js
<Navbar bg="dark" variant="dark">
<Container>
<Navbar.Brand href="/">Hype</Navbar.Brand>
<Navbar.Toggle aria-controls="basic-navbar-nav" />
<Navbar.Collapse id="basic-navbar-nav">
<Nav className="me-auto">
<Nav.Link href="/app">Home</Nav.Link>
<Nav.Link href="/app/dashboard">dashboard</Nav.Link>
</Nav>
<Nav className="ml-auto">
{!!keycloak.authenticated && (
<Nav.Link onClick={() => logoutHelper()}>
Logout ({keycloak.tokenParsed.preferred_username})</Nav.Link>)}
</Nav>
</Navbar.Collapse>
</Container>
</Navbar>
PrivateRouters.js
const PrivateRoutes = () => {
const { keycloak } = useKeycloak();
const isLoggedIn = keycloak.authenticated;
console.log("checking auth access " + isLoggedIn);
console.log(keycloak);
return isLoggedIn ? <Outlet/> : null;
};
Try to use
import { Link } from "react-router-dom";
Instead of using or <Nav.Link>.
You can easily wrap these inside of
<Link to='/app'><Nav.Link>Home</Nav.Link></Link>
Check example here: https://reactrouter.com/en/main/components/link
The SecuredPage component is rendering descendent routes, so the parent route must append a wildcard "*" route matcher to it's route so descendent routes can be matched and rendered.
function App() {
return (
<div>
<BrowserRouter>
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="/about" element={<AboutPage />} />
<Route path="/pricing" element={<PricingPage />} />
<Route path="/app/*" element={<SecuredPage />} /> // <-- append "*" to path
</Routes>
</BrowserRouter>
</div>
);
}
Descendent Routes components also build their paths relative to any parent Routes components, so the descendent path should not include any of the "path prefix" to this Routes.
const Secured = () => {
return (
<div>
<ReactKeycloakProvider
authClient={keycloak}
initOptions={{
onLoad: "login-required",
}}
LoadingComponent={<Loading />}
>
app landing page
<NavHomePage/>
<Routes>
<Route element={<PrivateRoutes />}>
<Route path="dashboard" element={<Dashboard />} />
</Route>
</Routes>
</ReactKeycloakProvider>
</div>
);
};
If you didn't want to use descendent routes you could convert Secured into a layout route component.
Example:
import { Outlet } from 'react-router-dom';
const KeycloakLayout = () => {
return (
<div>
<ReactKeycloakProvider
authClient={keycloak}
initOptions={{
onLoad: "login-required",
}}
LoadingComponent={<Loading />}
>
<NavHomePage/>
<Outlet />
</ReactKeycloakProvider>
</div>
);
};
function App() {
return (
<div>
<BrowserRouter>
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="/about" element={<AboutPage />} />
<Route path="/pricing" element={<PricingPage />} />
<Route path="/app" element={<KeycloakLayout />}>
<Route element={<PrivateRoutes />}>
<Route path="dashboard" element={<Dashboard />} />
</Route>
</Route>
</Routes>
</BrowserRouter>
</div>
);
}
No idea why it doesn't work for you, there should be an example that can be debugged. Learn from this youtube-channel how a good example works:
Youtube tutorial keycloak with react
Nevertheless, I see some things in your code that shouldn't be done like this anymore:
Do not use the react-keycloak/web library. It has not been serviced since 2021 and is no longer needed!
Check out the youtube links for a good example of keycloak authentication with routing without this react library.
Don't use <divs> in your code unless absolutely necessary. If you need divs, then use the <React.Fragments /> or <></> for short. Why is that important? If you want to debug your project in a browser in the future, these divs make the code very cluttered and tedious to debug.
For example here:
const Home = () => {
return (
<> //NO <div> USED
<NavMain/>
<h1 className="text-green-800 text-4xl">Welcome to the Homepage. Some more text123</h1>
</>
);
};
Another code:
function App() {
return (
//DONT USE DIV HERE. USELESSS
<BrowserRouter>
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="/about" element={<AboutPage />} />
<Route path="/pricing" element={<PricingPage />} />
<Route path="/app" element={<KeycloakLayout />}>
<Route element={<PrivateRoutes />}>
<Route path="dashboard" element={<Dashboard />} />
</Route>
</Route>
</Routes>
</BrowserRouter>
);
}
I have set up a login page for my web app but the site header component I created earlier appears on the top. It has a menu in it that leads to other pages, so if it's on the login page a user doesn't need to login when they can just click the menu option that will lead them to the home page.
I would like the site header to be invisible on the login, register and reset pages.
index.js
return (
<BrowserRouter>
<SiteHeader />
<Routes>
<Route path="/reviews/:id" element={ <MovieReviewPage /> } />
<Route path="/movies/home" element={<HomePage />} />
<Route path="/movies/favorites" element={<FavoriteMoviesPage />} />
<Route path="/movies/upcoming" element={<UpcomingMoviesPage />} />
<Route path="/movies/:id" element={<MoviePage />} />
<Route exact path="/" element={<LoginPage />} />
<Route exact path="/register" element={<RegisterPage />} />
<Route exact path="/reset" element={<ResetPage />} />
<Route path="*" element={ <Navigate to="/" /> } />
</Routes>
</BrowserRouter>
);
};
There are many ways to do that, but personally, I prefer to do so.
export const MyLayout = ({children}) => {
return (
<>
<SiteHeader />
{children}
</>
)
}
And after all, put your PageComponent inside component wherever you need.
For example.
const HomePage = () => {
return (
<MyLayout>
<div> This is Home Page </div>
</MyLayout>
)
}
Add an Authentication Context Provider. You could then read the context and render the header when you're logged in, else not.
https://codesandbox.io/embed/authentication-with-react-context-d3x0r
I was refactoring my React app after updating React Router to v6 and I got rid of the error I was getting in my routes, except now the desired layout is broken.
I need to include a permanent toolbar and a sidebar to be visible only in some pages. I tried to follow the docs but now the layout component is placed above all the pages it should be wrapping, not just overlapping them, but actually concealing them behind it.
The Layout component:
function Layout({ children }) {
return (
<div className="layout">
<Header />
<SidePanel />
<div className="main" style={{ marginTop: "100px" }}>
{children}
</div>
</div>
);
}
export default Layout;
The AppRouter component:
function AppRouter() {
return (
<Router>
<Routes>
<Route path="/" exact element={<Home />} />
<Route path="/login" element={<Login />} />
<Route path="/sign-up" element={<SignUp />} />
<Route element={<Layout />}>
<Route path="/diary" element={<Diary />} />
<Route path="/results" element={<Results />} />
<Route path="/details" element={<Details />} />
<Route path="/about" element={<About />} />
</Route>
</Routes>
</Router>
);
}
export default AppRouter;
Layout should render an Outlet for the children Routes to be rendered into.
import { Outlet } from 'react-router-dom';
function Layout() {
return (
<div className="layout">
<Header />
<SidePanel />
<div className="main" style={{ marginTop: "100px" }}>
<Outlet />
</div>
</div>
);
}
Outlet
An <Outlet> should be used in parent route elements to render their
child route elements. This allows nested UI to show up when child
routes are rendered.
I have a project in react/typescript. I have a react router that looks like this
const Root = () => (
<>
<NavBar/>
<Router>
<Route path="/" component={Home} />
<Route path="/timer" component={TimerPage} />
</Router>
</>
);
And I have a material-ui appbar that looks like this
export default function NavBar() {
return (
<div>
<AppBar position="static">
<Tabs>
<Tab label="Timer" to="/timer" component={TimerPage} />
</Tabs>
</AppBar>
</div>
);
}
There are a few issues - first the 'to' in Tab doesn't compile. Secondly, how do I make these two components work together, given they do very similar things?
If you are trying to navigate to another page, wrap your tab component, let react-router handle with the navigation and navigate using react-router history,
<Tabs value={value} onChange={handleChange} aria-label="simple tabs
example">
<div onClick={() => history.push("/timer")}>
<Tab label="Timer" />
</div>
</Tabs>
<Router>
<Route path="/" component={Home} />
<Route path="/timer" component={TimerPage} />
</Router>
Route should be inside Switch. Also, if you write path="/" this means that whatever page you will visit will still go to "home" page. This is because react-router does something like "least" checking of routes. So, if you had defined path "/images", before "/images/1", both will route you to "/images". Instead you could change the order of these paths, or add exact keyword before the first one.
Take a look at example below.
<Router>
<Switch>
<Route exact path="/" component={Home} />
<Route path="/timer" component={TimerPage} />
</Switch>
</Router>
or
<Router>
<Switch>
<Route path="/timer" component={TimerPage} />
<Route path="/" component={Home} />
</Switch>
</Router>
As for your second issue, you should put your AppBar (or div or any container) inside Router and assign Link to component property of Tab:
<Router>
<AppBar position="static">
<Tabs>
<Tab label="Timer" to="/timer" component={Link} />
</Tabs>
</AppBar>
</Router>
Keep in mind that Link component is imported from react-router and not from #mui.
consider the following example, I have a login page and an admin page. After logging in we will be redirected to admin page.
The admin page is shown as follows.
Desired behaviour: To render cars component in the admin component itself
Actual behaviour: On clicking cars or bikes component they are being rendered on a different page.
code is as follows
App.js
//imports here
function App() {
return(
<Router>
<Switch>
<Route exact path="/" component={Login} />
<Route exact path="/admin" component={Admin} />
<Route exact path="/cars" component={Cars} />
<Route exact path="/bikes" component={Bikes} />
</Switch>
</Router>
);
}
Admin.js
//imports here
const Admin = () => {
return (
<Fragment>
<div className="is-flex">
<Sidebar />
<Navbar />
</div>
</Fragment>
);
};
navbar.js
// imports here
const Sidebar = () => {
return (
<aside className="aside">
<p className="menu-label">Test Routes</p>
<ul className="menu-list">
<li>
<Link to="/cars">Cars</Link>
</li>
<li>
<Link to="/bikes">Bikes</Link>
</li>
</ul>
</aside>
);
};
Using react-router-dom ^5.1.2
Tried this but not able to understand what I missed? how to solve this problem?
Move your default route to the bottom of the stack. i.e.
function App() {
return(
<Router>
<Switch>
<Route path="/admin" component={Admin} />
<Route path="/cars" component={Cars} />
<Route path="/bikes" component={Bikes} />
<Route exact path="/" component={Login} />
</Switch>
</Router>
);
}