I have layout that header and footer components named as MainLayout.
This layout used in Home page.
I want to make another layout that named SubLayout.
This is not have header component, and used in About page.
How to make 2 different layout?
This is what I tried so far below.
Router.js
import React from 'react'
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom'
import MainLayout from '../components/layouts/MainLayout'
import Home from '../views/home/Home'
import About from '../views/about/About'
export default function Router1() {
return (
<Router>
<Switch>
<Route>
<MainLayout>
<Switch>
<Route path='/' exact component={Home} />
<Route path='/about' component={About} />
</Switch>
</MainLayout>
</Route>
</Switch>
</Router>
)
}
MainLayout
import React from 'react'
import Header from '../../components/layouts/Header'
import Footer from '../../components/layouts/Footer'
export default function Layout({ children }) {
return (
<div className="wrapper">
<Header />
<div className="container">
<div className="content">
{children}
</div>
</div>
<Footer />
</div>
)
}
SubLayout.js
import React from 'react'
import Footer from '../../components/layouts/Footer'
export default function Layout({ children }) {
return (
<div className="wrapper">
<div className="container">
<div className="content">
{children}
</div>
</div>
<Footer />
</div>
)
}
This is not a trivial problem and happens to a lot of website, especially after you have quite a bit pages.
Solution 1
Coding the layout in MainLayout is difficult since you basically have to fix every possible case of this website from now on, which is a challenge.
In order to avoid that, you can have a separate layout component for each page. ex.
export default function Page1() {
return (
<Header />
<YourPage1Content />
)
}
Although this is a bit extra work for each page, it's very flexible. And the solution is highly scalable no matter how many pages (or features, or sites) that you want to support. For example, you can even wire with entirely different header if you want for a particular page.
Solution 2
If you are looking for a generic way of solving this problem and still want to have a MainLayout since this is the wrapper for every pages, it can be done via Context.
export default function MainLayout() {
const { hideHeader } = useContext(LayoutContext)
return (
<>
{!hideHeader && <Header />}
{children}
</>
)
The context will be provided to you via route, or any custom Context.Provider which can be setup in your App.js or index.js
For instance for a particular route,
export default function MainLayout() {
const location = useLocation()
const hideHeader = location.pathname === '/'
return (
<>
{!hideHeader && <Header />}
{children}
</>
)
This approach is quite generic and highly scalable as well, as long as the same context is used for the entire site.
Summary
If you don't know what site you are building, use solution 1, it'll meet any requirement along the way. However if you have a specific need to meet, solution 2 is really pretty to make sure you can live with a generic Layout component.
All in all, it's about how many Layout component you'd like to reuse.
You can wrap either your whole component with the specific layout or you can wrap the route with specific layout to differentiate like,
export default function Router1() {
return (
<Router>
<Switch>
<MainLayout>
<Route path='/' exact component={Home} />
</MainLayout>
<SubLayout>
<Route path='/about' component={About} />
</SubLayout>
</Switch>
</Router>
)}
And there are unnecessary switch and route statements, cleaned it up a little.
Related
This question already has answers here:
How do I render components with different layouts/elements using react-router-dom v6
(2 answers)
Closed 8 months ago.
So my website is gonna have three differently styled views for account types student, teacher and admin (its a dummy e-learning platform). I want to have a different styled navbar for student (two different versions for logged in and logged out) then a different one for teacher (again one for logged in and logged out) and then another one for admin(single one for logged in).
This is what Im doing right now. I made a layout component which warps everything.
const Layout: FC<LayoutProps> = (props) => {
return (
<div className="flex flex-col min-h-screen dark:bg-gray-800">
<MainNavigationBar />
<main className="mx-auto flex-grow" style={{ width: "90%" }}>
{props.children}
</main>
<Footer />
</div>
);
};
export default Layout;
Then this is what my App.tsx looks like.
export default function App() {
return (
<Layout>
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="/signup" element={<SignUpPage />} />
<Route path="/login" element={<LogInPage />} />
<Route path="/cart" element={<EmptyCart />} />
<Route path="/cart/:studentId" element={<ShoppingCart />} />
<Route path="/checkout/:studentId" element={<CheckOutPage />} />
</Routes>
</Layout>
);
}
As you can see there are no routes for the teacher and admin yet because idk what is the best was to implement the functionality where each account type has different Navbar essentially meaning a totally different view style.
I have thought of two ways but help me understand which one is better.
I put navbar directly inside each pages component. but that would mean that there will be copies of nav bar on each page for the account types and then updating changes would be hard, feels inefficent.
I put all three nav bars in the App.tsx file and then conditionally render nav bar depending on which account type is active. this sounds better cause there will only be one nav bar copy per all account type pages. but problem with this is that idk how to trigger/set "account type active" when "/teacher" or "/admin" is visited.
Help me understand the best practice or technique, this is my first big project, thank you.
You should conditionally render the header based on the type of user that is logged in. Based on the routes, I'm guessing your app will have some sort of user auth. You could have a global state using either Context API or a library like Redux, in which you'd save your logged-in users' account data (which includes the account type). You could then conditionally render a navbar that corresponds to the account type.
You can learn more about context here, and Redux here.
I was having the same scenario and I solved it by fetching the type of user :
const { user } = props;
switch (user?.role) {
case "STUDENT":
return <Student />;
case "TEACHER":
return <Teacher />;
case "ADMIN":
return <Admin />;
default:
return <div className="">User not valid.</div>;
}
I am sharing you my code :
import React from "react";
import { Switch, Route } from "react-router-dom";
import Profile from "../pages/Profile";
import Dashboard from "../pages/Dashboard";
import {Student, Teacher, Admin} from "./User";
import { useSelector } from "react-redux";
const Content = (props) => {
const { user } = props;
if (!user) {
return <div className="">User not valid.</div>;
}
switch (user?.role) {
case "STUDENT":
return <Student />;
case "TEACHER":
return <Teacher />;
case "ADMIN":
return <Admin />;
default:
return <div className="">User not valid.</div>;
}
};
export default function Routes() {
const user = useSelector((s) => s.auth.user);
return (
<Switch>
<Route exact path={"/profile"} component={Profile} />
<Route exact path={"/dashboard"} component={Dashboard} />
<Content user={user}/>
</Switch>
);
}
You can take help from my above code .
I have created one simple project with 3 main components/pages.
Home page (path: "/") with is login page. once we click on login button, will create a dynamic URL "admin/random_num
LogIn.js
import { Link, Route } from "react-router-dom";
import Dashboard from "../Admin/Dashboard";
export default function Login(props) {
return (
<div>
<h1>Login form</h1>
<Link to={`/admin/${Math.random()}`}>Login Button</Link>
<Route path="/admin/:aid" component={Dashboard} />
</div>
);
}
2nd component/page is admin(path: "/admin/randum_num" , eg: "/admin/132"), which should be displayed when clicked on login button on login page.
Admin.js
export default function Dashboard(props) {
return (
<div>
<h1>Admin Dashboard</h1>
</div>
)
}
3rd component is 404page, which will simply display when no route is matched.
Error404.js
export default function Error404() {
return (
<div>
<h1>404</h1>
</div>
)
}
last not the least, in App.js i am setting up routing.
App.js
import React from 'react'
import './App.css';
import Error404 from './pages/Error404';
import Login from './pages/StartingScreen/Login';
function App() {
return (
<BrowserRouter>
<div className="App">
<Switch>
<Route exact path="/" component={Login} />
<Route component={Error404} />
</Switch>
</div>
</BrowserRouter>
);
}
export default App;
Note: i am able to display the admin dashboard page if i remove "exact" parameter in Route statement but after removing "exact" both Login and Dashboard page will be visible which i dont want. I want only one component should be visible at given route.
I am struggling to find an answer regarding this problem since one week, kindly help. If the given detail is incomplete, let me know.
Since you are using exact for your login component, that means your Login component will only render for the root path /.
And your Dashboard component is inside your Login component, that means you also need to add "/admin/:aid" to your login route.
You need to change this
<Route exact path="/" component={Login} />
to
<Route exact path={["/", "/admin/:aid"]} component={Login} />
Update:
To hide login button, you can use another Switch in your Login component
export default function Login(props) {
return (
<div>
<Switch>
<Route exact path="/">
<h1>Login form</h1>
<Link to={`/admin/${Math.random()}`}>Login Button</Link>
</Route>
<Route exact path="/admin/:aid" component={Dashboard} />
</Switch>
</div>
);
}
i am trying to use nested routing inside my application. in this case shop page is located on /shop url and i want to render CollectionsOverview on /shop/ and CollectionPage on /shop/:collection and the CollectionsOverview is working fine but
CollectionPage is not working. i even tried to use hardcoded path like /shop/abc but its still not working.. please help
version of my react-router-dom is ^5.0.0
import React from 'react';
import {Route, Switch,Router} from 'react-router-dom';
function CollectionPage() {
return (
<div className="App">
CollectionPage
</div>
);
}
function CollectionsOverview() {
return (
<div className="App">
CollectionsOverview
</div>
);
}
export default function ShopPage({match}){
return(
<div className='shop-page'>
<Router>
<Switch>
<Route exact path={`${match.path}`} component={CollectionsOverview}/>
<Route exact path={`${match.path}/:collectionId `} component={CollectionPage}/>
</Switch>
</Router>
</div>
)
}
Try this, since you already have a Router wrapping your App component, you don't need another one having it's own context in the ShopPage, since it's -supposedly- is originally routed from App, in the context of the Router that lives in the App
export default function ShopPage({match}){
return (
<div className='shop-page'>
<Switch>
<Route exact path={`${match.path}`} component={CollectionsOverview}/>
<Route exact path={`${match.path}/:collectionId `} component={CollectionPage}/>
</Switch>
</div>
);
}
You'll need the Switch only to determine navigating decision for it's Route components
I need to maintain a section of my app available for all pages (most of the pages actually)
to do that I wrapped the routes in a Layout
<Router>
<Route path="/login" exact strict component={Login}/>
<Layout>
<Route path="/list" exact strict component={List}/>
<Route path="/settings" exact strict component={Settings}/>
</Layout>
</Router>
the Layout component looks like this
class Layout extends Component {
render() {
return (
<React.Fragment>
<div>this box should be visible in all pages using layout</div>
<div>{this.props.children}</div>
</React.Fragment>
)
}
}
this work perfectly fine. the only poblem is when I go to /login
is rendering the Login component and also the Layout component.
I shouldn´t render the Layout if is not for one of the ¨protected¨ routes.
is there a way to achieve that without moving the layout inside of the List of Settings component?
the reason for this is because the box that´s fixed in all pages makes one API request and there´s no need to do that same API call for /list and /settings. I do it only once.
hope this makes sense to you. thank you!
update:
"react-router-dom": "^5.0.1"
I've done a fair bit of research on the subject and came up with this. I have it set up in my routes:
import React from 'react'
import { BrowserRouter as Router, Redirect, Route, Switch } from 'react-router-dom'
// import PublicRoute, PrivateRoute, Login, Private
<Router>
<Switch>
<PublicRoute exact path='/login' component={Login} />
<PrivateRoute path='/private' component={Private} />
</Switch>
<Router>
Then in my privateRoute.js:
import React from 'react'
import { Redirect, Route } from 'react-router-dom'
// import useAuth and PrivateLayout
export default ({ componet: Component, ...rest }) => {
const { token } = useAuth() // wrapper I wrote to handle auth
if (!token) return <Redirect to='/login' />
return (
<Route {...rest} render={props => (
<PrivateLayout><Component {...props} /></PrivateLayout>
)} />
)
}
And my publicRoute.js is similar but without authentication. My layouts are a simple wrapper, all you need to do is include children:
export default ({ children }) => {
return (
<>
<Topbar />
<Sidebar />
{children}
</>
)
Note: For those who don't know <> is the shorthand for <React.Fragment>.
im trying to format my HTML like so within the body tag:
<header id="header"></header>
<main id="app"></main>
<footer id="footer"></footer>
reason why is so that i have my navigation out of <main></main> and in <header></header>
Im also rendering the corresponding React component individually i.e: document.getElementById("header"), document.getElementById("app") ...:
ReactDOM.render(
<Header />,
document.getElementById("header")
);
When clicking <Link to="/log-in"></Link> in <Header /> it breaks out of SPA and jumps to /log-in page.
What am i missing here?
Using ReactDOM.render multiple times will create separate instances unaware of each other source.
Let's go on about restructuring that a bit to make your app feel better:
App.js
import React from 'react';
import { Main } from './components';
const App = () => (
<Main />
)
ReactDOM.render(<App />, document.getElementById("app"));
Main.js
import React from 'react';
import { Router, Route } from 'react-router-dom';
import { Header, Login, Register, Home } from './components'
const Main = () => (
<Router>
<React.Fragment>
<Header />
<Route exact path="/" component={Home} />
<Route path="/login" component={Login} />
<Route path="/register" component={Register} />
<Footer />
</React.Fragment>
</Router>
)
export { Main };
So this way, we're ever only really rendering one instance. Your header/footer would be placed outside of the router, so whenever the route changes, they remain unaffected. Keep in mind that this will present a challenge, eg if you want your Header to highlight which route is active, the simples way, since it's outside of router and doesn't receive props from it, is to check the url. Also note that we're using <React.Fragment> here, but you're free to use a <div> or anything else you like. Router expect one child only, so if you don't want additional html elements, you can use a fragment!