I have my app and I want to add an admin routes. The problem is my header and footer are rendered on every route so when I'm trying to access admin panel they are rendered too. How can I separate 2 different routes for 2 different apps (not exactly but I hope u'll understand).
This is how my router looks like:
<Router>
<Container>
<Header/> // it is intended
<Switch>
<Route exact path='/' component={ Home } />
<Route path='/news/:category/:id/:title' component={ SingleArticle } />
<Route path='/news' component={ Home } />
<Route path='/live' component={ Live } />
<Route path='/admin' component={ AdminPanel } /> //here I want all my admin routes which generates its own header and footer
<Route path='*' component={ NotFound } />
</Switch>
<Footer /> // it is intended
</Container>
</Router>
You can have different routes for admin and non-admin. You can do following:
if(admin) {
return (
<Router>
<Header>
// ... routes here
</Header>
</Router>
)
} else {
return (
<Router>
<AdminHeader />
// ... routes
</Router>
)
}
You can make use of a layout type component that takes a component as a prop, adds the necessary components to it and returns an new component like so:
const Layout = ({ children }) => {
return (
<section>
<Header/>
{children}
<Footer/>
</section>
)
}
Then any component you want to render with the header and footer you can declare as a child of the layout component:
<Router>
<Container>
<Switch>
<Route path='/admin' component={ AdminPanel } /> //here I want all my admin routes which generates its own header and footer
<Layout>
<Route exact path='/' component={ Home } />
<Route path='/news/:category/:id/:title' component={ SingleArticle } />
<Route path='/news' component={ Home } />
<Route path='/live' component={ Live } />
<Route path='*' component={ NotFound } />
</Layout>
</Switch>
</Container>
</Router>
You can use useLocation() Hook to find paths and separate routes.
eg:
import { useLocation } from 'react-router-dom'
if (useLocation().pathname.includes("/admin")) {
return (
<Switch>
<Route exact path="/admin" component={Admin} />
</Switch>
);
} else {
return (
<div className="App">
<Navbar />
<Switch>
// Routes goes
</Switch>
</div>
);
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'm trying to upgrade to react-router-dom v6 :
v5
In version 5 it works like a charm:
App.js
import Sidebar from "./components/sidebar/Sidebar";
import Topbar from "./components/topbar/Topbar";
import "./app.css";
import Home from "./pages/home/Home";
import {
BrowserRouter as Router,
Switch,
Route,
} from "react-router-dom";
import UserList from "./pages/userList/UserList";
import User from "./pages/user/User";
import NewUser from "./pages/newUser/NewUser";
import ProductList from "./pages/productList/ProductList";
import Product from "./pages/product/Product";
import NewProduct from "./pages/newProduct/NewProduct";
import Login from "./pages/login/Login";
function App() {
const admin = JSON.parse(JSON.parse(localStorage.getItem("persist:root"))?.user || "{}")?.currentUser?.isAdmin ;
return (
<Router>
<Switch>
<Route path="/login">
<Login />
</Route>
{admin && (
<>
<Topbar />
<div className="container">
<Sidebar />
<Route exact path="/">
<Home />
</Route>
<Route path="/users">
<UserList />
</Route>
<Route path="/user/:userId">
<User />
</Route>
<Route path="/newUser">
<NewUser />
</Route>
<Route path="/products">
<ProductList />
</Route>
<Route path="/product/:productId">
<Product />
</Route>
<Route path="/newproduct">
<NewProduct />
</Route>
</div>
</>
)}
</Switch>
</Router>
);
}
export default App;
v6
When upgraded to v6 I changed my code to be like this:
<Routes>
<Route path="/login" element={<Login />} />
{admin && (
<>
<Route path="/" element={<Topbar />}/>
<Route path="/" element={
<>
<div className="container">
<Route index element={<Sidebar/>}/>
<Route index element={<Home/>}/>
<Route path="/users" element={<UserList />} />
<Route path="/user/:userId" element={<User />} />
<Route path="/newUser" element={<NewUser />} />
<Route path="/productList" element={<ProductList />} />
<Route path="/product/:productId" element={<Product />} />
<Route path="/newProduct" element={<NewProduct />} />
</div>
</>
}
</>
)}
</Routes>
This is my css file for App.js
Notice: the Topbar component should be outside the div, and react router didn't recognize the components inside the as routes even without div, that means each component should have a unique path, I tried also two components with the same path like this:
<Route path="/" element = {<><Home/><Sidebar/><>}, but the css is not taking effect
.container {
display: flex;
margin-top: 50px;
}
It doesn't work. I tried different code and I searched a lot without finding any solution.
Part of the issue is that you are rendering multiple identical paths, i.e. two "/" paths and two nested index paths. This won't work.
In react-router-dom v6 you can create what are called layout components. The layout components can render your headers and footers, sidebars, drawers, and general content layout elements, and importantly an Outlet component for the nested/wrapped Route components to be rendered into.
Example:
import { Outlet } from 'react-router-dom';
const AppLayout = ({ admin }) => admin ? (
<>
<Topbar />
<div className="container">
<Sidebar />
<Outlet />
</div>
</>
) : null;
Render the layout component into a Route wrapping the routes you want to be rendered into the specific layout.
<Routes>
<Route path="/login" element={<Login/>} />
<Route element={<AppLayout admin={admin} />}>
<Route index element={<Home />} />
<Route path="/users" element={<UserList />} />
<Route path="/user/:userId" element={<User />} />
<Route path="/newUser" element={<NewUser />} />
<Route path="/productList" element={<ProductList />} />
<Route path="/product/:productId" element={<Product />} />
<Route path="/newProduct" element={<NewProduct />} />
</Route>
</Routes>
I will share working code from my project, hope this will help you.
Try to create a component layout that should look something like this:
// Layout.js
import React from "react";
import { NavBar } from "./SidebarNav";
export const Layout = ({ children }) => {
return (
<>
<div className="block">
<NavBar />
<div className="w-full ">{children}</div>
</div>
</>
);
};
and then create routes in a similar way:
// routes.js
import { Routes, Route } from "react-router-dom";
import { Layout } from "./layout/Layout";
import Home from "./pages/Home";
import { ItemList } from "./pages/ItemList";
const BaseRouter = () => (
<>
<Layout>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/item-list/" element={<ItemList />} />
</Routes>
</Layout>
</>
);
export default BaseRouter;
Splitting routes into a separate file gives you more freedom and, above all, makes your code more accessible.
// App.js
import { BrowserRouter as Router } from "react-router-dom";
import BaseRouter from "./routes";
function App() {
return (
<Router>
<BaseRouter />
</Router>
);
}
export default App;
I have problem with common header in react js.
Currently login route is displaying common header and i dont want to show on my login page. If i go to contacts page than its showing common header which is perfect
import "./styles/App.scss";
import Navbar from "./components/elements/Navbar";
import Contacts from "./components/contacts/Contacts";
import { Provider } from "react-redux";
import store from "./store";
import { BrowserRouter as Router, Route, Switch } from "react-router-dom";
import AddContact from "./components/contacts/AddContact";
import EditContact from "./components/contacts/EditContact";
import Login from "./components/login/Login";
import Logout from "./components/logout/Logout";
function App(props) {
return (
<Provider store={ store }>
<Router>
<div className="App">
{props.location.pathname !== '/login' ? <Navbar/> : null}
<Route exact path="/login" component={ Login } />
<div className="container">
<div className="py-3">
<Switch>
<Route exact path="/" component={ Contacts } />
<Route exact path="/logout" component={ Logout } />
<Route exact path="/contacts/add" component={ AddContact } />
<Route
exact
path="/contacts/edit/:id"
component={ EditContact }
/>
</Switch>
</div>
</div>
</div>
</Router>
</Provider>
);
}
export default App;
[1]: https://i.stack.imgur.com/7V0qi.png
I think you meant to create Links in NavBar, not declare the Routes:
<Router>
<div className="App">
<div className="container">
<div className="py-3">
<Switch>
<Route exact path="/login" component={Login} />
<Route>
<Navbar />
<Switch>
<PrivateRoute exact path="/" component={Contacts} />
<PrivateRoute exact path="/logout" component={Logout} />
<PrivateRoute exact path="/contacts/add" component={AddContact} />
<PrivateRoute
exact
path="/contacts/edit/:id"
component={EditContact}
/>
</Switch>
</Route>
</Switch>
</div>
</div>
</div>
</Router>
NavBar:
function Navbar() {
const someId = 123 // example
return (
<>
<Link to="/">Login</Link>
<Link to="/logout">Logout</Link>
<Link to="/contacts/add">Add Contacts</Link>
<Link to={`/contacts/edit/${someId}`}>Edit Contact</Link>
</>
)
}
After this, you are most likely to be looking for authentication. I recently wrote an answer on authenticated or protected routes i.e. PrivateRoute.
Also, note that All children of a Switch should be Route or Redirect elements.
You can only show the <Navbar/> when it's not the login page and you need to keep the login page inside the switch for the pages to work.
return (
<Provider store={ store }>
<Router>
<div className="App">
{props.location.pathname !== '/login' ? <Navbar/> : null}
<div className="container">
<div className="py-3">
<Switch>
<Route exact path="/login" component={ Login } />
<Route exact path="/" component={ Contacts } />
<Route exact path="/logout" component={ Logout } />
<Route exact path="/contacts/add" component={ AddContact } />
<Route
exact
path="/contacts/edit/:id"
component={ EditContact }
/>
</Switch>
</div>
</div>
</div>
</Router>
</Provider>
);
In my code, I want Header & Footer components to be rendered in all routes except '/login'so how to do that? How to hide component on a specific route?
const AppRouter = () => {
return (
<BrowserRouter>
<div>
<Header />
<Switch>
<Route path="/login" component={Login} /> {/* I wanna render this route without Header & Footer */}
<Route path="/" component={Home} exact />
<Route path="/product" component={ProductOverview} />
<Route path="/profile" component={Profile} />
<Route component={NotFound} />
</Switch>
<Footer />
</div>
</BrowserRouter>
);
};
I am trying to separate my routes depending on views. To be specific I have created an admin view where I am going to have different routes for admin only. I am trying to use multiple switches but I keep getting 404.
The following is how I am doing:
App.js
<div className="App">
<Switch>
<Route exact path="/admin/dashboard" component={ AdminPanel } />
<Route component={ NoMatch } />
</Switch>
</div>
AdminPanel.js
<Link to="/admin/dashboard" className="sidebar-item">...</Link>
<Link to="/admin/dashboard/exams" className="sidebar-item">...</Link>
.
.
.
<div className="admin-content">
<Switch>
<Route exact path="/admin/dashboard" component={ Dashboard } />
<Route exact path="/admin/dashboard/exams" component={ Exam } />
</Switch>
</div>
When I visit localhost:3000/admin/dashboard I get to see the Dashboard component. But when I navigate to localhost:3000/admin/dashboard/exams I am greeted with the 404 page.
Where am I going wrong?
App.js:
<Switch>
<Route path="/admin/dashboard" component={ AdminPanel } />
<Route path="/404" component={ NoMatch } />
<Route component={ NoMatch } />
</Switch>
AdminPanel.js
import {Redirect} from 'react-router-dom'
<Switch>
<Route exact path="/admin/dashboard" component={ Dashboard } />
<Route exact path="/admin/dashboard/exams" component={ Exam } />
<Route render={({location}) => <Redirect to={{
pathname: '/404',
state: { originalUrl: location.pathname}
}} />} />
</Switch>
later in the NoMatch component, you can access it under props.location.state.originUrl