Using multiple layouts for react-router components - javascript

If I have the following:
<Route path="/" component={Containers.App}>
{ /* Routes that use layout 1 */ }
<IndexRoute component={Containers.Home}/>
<Route path="about" component={Containers.About}/>
<Route path="faq" component={Containers.Faq}/>
<Route path="etc" component={Containers.Etc}/>
{ /* Routes that use layout 2 */ }
<Route path="products" component={Containers.Products}/>
<Route path="gallery" component={Containers.Gallery}/>
</Route>
How can I make it so that the two sets of routes each use a different layout.
If I only had a single layout then I would put it in App, but in this case where do I define the layout?
To make it even more complicated some of the layout components (eg top nav) are shared between both layout types.

You can use routes without a path to define containers that are not defined by the url:
<Route path="/" component={Containers.App}>
{ /* Routes that use layout 1 */ }
<Route component={Containers.Layout1}>
<IndexRoute component={Containers.Home}/>
<Route path="about" component={Containers.About}/>
<Route path="faq" component={Containers.Faq}/>
<Route path="etc" component={Containers.Etc}/>
</Route>
<Route component={Containers.Layout2}>
{ /* Routes that use layout 2 */ }
<Route path="products" component={Containers.Products}/>
<Route path="gallery" component={Containers.Gallery}/>
</Route>
</Route>
The layout components can then import additional components such as the top nav

Route's path property has accepted an array of strings for a while now. See https://github.com/ReactTraining/react-router/pull/5889/commits/4b79b968389a5bda6141ac83c7118fba9c25ff05
Simplified to match the question routes, but I have working multiple layouts essentially like this (using react-router 5):
<App>
<Switch>
<Route path={["/products", "/gallery"]}>
<LayoutTwo>
<Switch>
<Route path="/products" component={Products} />
<Route path="/gallery" component={Gallery} />
</Switch>
</LayoutTwo>
</Route>
{/* Layout 1 is last because it is used for the root "/" and will be greedy */}
<Route path={["/about", "/faq", "/etc", "/"]}>
<LayoutOne>
<Switch>
<IndexRoute component={Home} />
<Route path="/about" component={About} />
<Route path="/faq" component={Faq} />
<Route path="/etc" component={Etc} />
</Switch>
</LayoutOne>
</Route>
</Switch>
</App>
This solution prevents re-mounting the layouts on route changes, which can break transitions, etc.

Here's a great way to use multiple layouts with different React components.
In your router you can use:
<Router history={browserHistory}>
<Route component={MainLayout}>
<Route path="/" component={Home} />
<Route path="/about" component={About} />
</Route>
<Route component={EmptyLayout}>
<Route path="/sign-in" component={SignIn} />
</Route>
<Route path="*" component={NotFound}/>
</Router>
Source: https://sergiotapia.me/different-layouts-with-react-router-71c553dbe01d

Pintouch, I was able to get this working with the following example:
Layout1:
import React from 'react'
const Layout1 = (props) => (
<div>
<h1>Layout 1</h1>
{props.children}
</div>
)
export default Layout1
Layout2:
import React from 'react'
const Layout2 = (props) => (
<div>
<h1>Layout 2</h1>
{props.children}
</div>
)
export default Layout2
Layout Container:
import React from 'react'
const LayoutContainer = (props) => (
<div>
{props.children}
</div>
)
export default LayoutContainer
Routes:
import React from 'react';
import { Router, Route, IndexRoute, hashHistory } from 'react-router';
import LayoutContainer from './LayoutContainer'
import Layout1 from './Layout1'
import Layout2 from './Layout2'
import ContactManagerView from './ContactManagerView'
import CallerIdView from './CallerIdView'
import NotFound from './NotFound'
<Router history={hashHistory}>
<Route path="/" component={LayoutContainer}>
<Route component={Layout1}>
<IndexRoute component={DashboardView}/>
<Route path='Contacts' component={ContactManagerView}/>
</Route>
<Route component={Layout2}>
<Route path='CallerId' component={CallerIdView}/>
</Route>
<Route component={Layout}>
<Route path='*' component={NotFound}/>
</Route>
</Route>
</Router>

This is how it's done in React Router v6:
<Route path="/" element={<App />}>
{/* Routes that use layout 1 */}
<Route element={<BlueLayout />}>
<Route index element={<Home />} />
<Route path="about" element={<About />} />
<Route path="faq" element={<Faq />} />
<Route path="etc" element={<Etc />} />
</Route>
{/* Routes that use layout 2 */}
<Route element={<RedLayout />}>
<Route path="products" element={<Products />} />
<Route path="gallery" element={<Gallery />} />
</Route>
</Route>
I've created a complete example in StackBlitz here.

You could create a function RouteWithLayout that renders the <Route> wrapped within the layout:
const RouteWithLayout = ({ component: Component, layout: Layout, ...rest }) => (
<Route {...rest} render={props => (
<Layout>
<Component {...props} />
</Layout>
)} />
)
const MainLayout = props => (
<div>
<h1>Main</h1>
{props.children}
</div>
)
const AltLayout = props => (
<div>
<h1>Alt</h1>
{props.children}
</div>
)
const Foo = () => (
<p>Foo</p>
)
const Bar = () => (
<p>Bar</p>
)
const App = () => (
<div>
<Switch>
<RouteWithLayout exact path="/foo" layout={MainLayout} component={Foo} />
<RouteWithLayout exact path="/bar" layout={AltLayout} component={Bar} />
</Switch>
</div>
)

I came across this question and found a solution that I want to share.
With react router v4 we could render the routes directly in your layout. Which is more readable and easy to maintain.
Layout
export class MainLayout extends React.PureComponent {
render() {
return (
<div>
<Header />
{this.props.children}
<Footer />
</div>
);
}
}
Mainlayout.propTypes = {
children: PropTypes.node.isRequired,
}
Router
export default function App() {
return (
<Switch>
<MainLayout>
<Switch>
<Route path="/" component={Home} />
<Route path="/about" component={About} />
</Switch>
</MainLayout>
<OtherLayout>
.... other paths
</OtherLayout>
</Switch>
);
}

Related

React routing based on id

I work with react-router v6. I want to create a new route based on an id. On my homepage "example.com" I have a selection of articles. I want to create a detail view for each article "example.com/{id}". My approach looks like this:
App.tsx
import {
BrowserRouter,
Routes,
Route,
} from "react-router-dom";
import ProjectWrapper from "./components/ProjectWrapper";
import Home from "./pages/Home";
import PageError from "./pages/PageError";
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Home />} >
<Route path=":id" element={<ProjectWrapper />} />
</Route>
<Route path="*" element={<PageError />} />
</Routes>
</BrowserRouter>
);
}
ProjectWrapper.tsx
import { useParams } from "react-router-dom";
import { ProjectList } from "../pages/algorithms/ProjectList";
import PageError from "../pages/PageError";
function ProjectWrapper() {
const { id } = useParams();
const project = ProjectList.find((project) => project.id.toLowerCase() === id?.toLowerCase());
if (project === undefined) return (<PageError />);
return (
<div>
{project!.shortDescription}
</div>
)
}
export default ProjectWrapper;
Am I missing something ?
With the given routing code:
<Routes>
<Route path="/" element={<Home />} >
<Route path=":id" element={<ProjectWrapper />} />
</Route>
<Route path="*" element={<PageError />} />
</Routes>
Home is what is called a Layout Route. Layout routes render some common logic and UI, and an Outlet component for nested Routes to render their content when matched.
Example:
import { ..., Outlet } from 'react-router-dom';
const Home = () => {
...
return (
<>
...
<Outlet /> // <-- nested routes render content here
</>
);
};
If you don't want the Home component to render along with children routes then place it on its own route.
Examples:
<Routes>
<Route path="/" element={<Home />} />
<Route path="/:id" element={<ProjectWrapper />} />
<Route path="*" element={<PageError />} />
</Routes>
or
<Routes>
<Route path="/">
<Route index element={<Home />} />
<Route path=":id" element={<ProjectWrapper />} />
</Route>
<Route path="*" element={<PageError />} />
</Routes>

useParams() is empty in react-router

I'm trying to render the params in the url as an h2 in in the website. But even when I try console.log useParams is empty.
Here's my Router.js file
const Webpages = () => {
return (
<Router>
<Routes>
<Route exact path="/" element={Home()} />
<Route exact path="/comics" element={Comics()} />
<Route path='/comics/:comicId' element={Comic()} /> <--------------
<Route exact path="/portfolio" element={Portfolio()} />
<Route exact path="/blog" element={Blog()} />
<Route exact path="/contact" element={Contact()} />
<Route exact path="/store" element={Store()} />
<Route path="*" element={NotFound()} />
</Routes>
</Router>
);
};
export default Webpages;
Here's my comic component
import React from 'react';
import NavBar from '../../components/NavBar';
import { useParams } from 'react-router-dom';
function Comic() {
let { comicId } = useParams();
console.log(comicId);
return (
<div>
<NavBar />
<p>{comicId}</p>
</div>
)
}
export default Comic
The page element when I go to a random comic works fine, like localhost:3000/comics/465456 but the tag is empty and the console log is undefined, it's also undefined if I just try to console log useParams()
You are directly invoking the React function. Directly invoking React functions is not how React works.
The Route component's element prop expects a React.ReactNode, a.k.a. JSX. Pass the components as JSX. The JSX is transpiled down to vanilla Javascript and the React framework handles calling your function within the confines of the React component lifecycle.
Example:
const Webpages = () => {
return (
<Router>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/comics" element={<Comics />} />
<Route path='/comics/:comicId' element={<Comic />} />
<Route path="/portfolio" element={<Portfolio />} />
<Route path="/blog" element={<Blog />} />
<Route path="/contact" element={<Contact />} />
<Route path="/store" element={<Store />} />
<Route path="*" element={<NotFound />} />
</Routes>
</Router>
);
};

PrivateRoute in React-router-dom v6 isnt working

I'm trying to use ProtectedRoute as
I can't see why the code isn't working, I'm not getting any error, but at /account it should display <Profile/> and it's blank, I can see the header and footer, but the whole <Profile/> is missing
before trying to use a PrivateRoute, I could display Profile with any problem.
my ProtectedRoute.js
import React from "react";
import { useSelector } from "react-redux";
import { Navigate, Outlet } from "react-router-dom";
const ProtectedRoute = () => {
const {isAuthenticated} = useSelector((state)=>state.user)
return isAuthenticated ? <Outlet /> : <Navigate to="/login"/>
}
export default ProtectedRoute;
my app.js
function App() {
const {isAuthenticated, user} = useSelector(state=>state.user)
React.useEffect(() => {
WebFont.load({
google:{
families: [ "Droid Sans", "Chilanka"],
},
});
store.dispatch(loadUser())
}, []);
return (
<Router>
<Header/>
{isAuthenticated && <UserOptions user={user} />}
<Routes>
<Route exact path="/" element={<Home/>}/>
<Route exact path="/product/:id" element={<ProductDetails/>}/>
<Route exact path="/products" element={<Products/>}/>
<Route path="/products/:keyword" element={<Products/>}/>
<Route exact path="/search" element={<Search/>}/>
<Route exact path="/account" element={<ProtectedRoute/>}/>
<Route exact path="/account" element={<Profile/>}/>
<Route exact path="/login" element={<LoginSignUp/>}/>
</Routes>
<Footer/>
</Router>
);
}
export default App;
and my Profile
const Profile = () => {
const { user, loading, isAuthenticated} = useSelector((state) => state.user);
const navigate = useNavigate();
useEffect(() => {
if(isAuthenticated === false){
navigate("/login");
}
}, [navigate,isAuthenticated])
return (
<Fragment>
<MetaData title={`${user.name}'s Profile`} />
<div className="profileContainer">
<div>
<h1>My Profile</h1>
<img src={user.avatar?.url} alt={user.name} />
<Link to="/me/update">Edit Profile</Link>
</div>
<div>
<div>
<h4>Full Name</h4>
<p>{user.name}</p>
</div>
<div>
<h4>Email</h4>
<p>{user.email}</p>
</div>
<div>
<h4>Joined On</h4>
<p>{String(user.createdAt).substr(0, 10)}</p>
</div>
<div>
<Link to="/orders">My Orders</Link>
<Link to="/password/update">Change Password</Link>
</div>
</div>
</div>
</Fragment>
);
};
export default Profile;
You are rendering two routes for the same "/account" path. ProtectedRoute is rendered on its own self-closing route, so the second route rendering Profile is unreachable.
<Routes>
<Route exact path="/" element={<Home/>}/>
<Route exact path="/product/:id" element={<ProductDetails/>}/>
<Route exact path="/products" element={<Products/>}/>
<Route path="/products/:keyword" element={<Products/>}/>
<Route exact path="/search" element={<Search/>}/>
<Route exact path="/account" element={<ProtectedRoute/>}/>
<Route exact path="/account" element={<Profile/>}/> // <-- unreachable, oops!
<Route exact path="/login" element={<LoginSignUp/>}/>
</Routes>
Remove the path prop from the layout route rendering the ProtectedRoute and ensure it is actually wrapping other Route components. You may as well also remove the exact prop on all the routes as this prop was removed in RRDv6.
Example:
<Router>
<Header/>
{isAuthenticated && <UserOptions user={user} />}
<Routes>
<Route path="/" element={<Home />} />
<Route path="/product/:id" element={<ProductDetails />} />
<Route path="/products" element={<Products />} />
<Route path="/products/:keyword" element={<Products />} />
<Route path="/search" element={<Search />} />
<Route element={<ProtectedRoute />}>
<Route path="/account" element={<Profile />} /> // <-- wrapped by layout route!
</Route>
<Route path="/login" element={<LoginSignUp />} />
</Routes>
<Footer/>
</Router>
The ProtectedRoute component doesn't appear to wait for the user state to populate before rendering either the Outlet for protected content or the redirect. Apply some conditional rendering to render a loading indicator or similar while the user state is populated.
import React from "react";
import { useSelector } from "react-redux";
import { Navigate, Outlet } from "react-router-dom";
const ProtectedRoute = () => {
const user = useSelector((state) => state.user);
if (!user) return null; // <-- or loading indicator, etc...
return user.isAuthenticated
? <Outlet />
: <Navigate to="/login" replace />;
}
export default ProtectedRoute;

Nested route in React Router reloads the entire page

I am trying to setup nesting in the react router. I have the following code:
import React from 'react';
import DefaultSwitch from './components/DefaultSwitch/DefaultSwitch';
import './scss/App.scss';
const App = () => {
return (
<DefaultSwitch />
);
};
export default App;
With DefaultSwitch defined as:
const DefaultSwitch = () => {
return (
<Switch>
<Route exact path='/' component={Landing} />
<Route exact path='/login' component={Login} />
<Route exact path='/logout' component={Logout} />
<Route path="/dashboard" component={Dashboard} />
</Switch>
);
}
Inside the Dashboard I have the following:
const Dashboard = () => {
return (
<div>
<MyNavbar />
<DashboardSwitch />
</div>
);
};
And finally DashboardSwitch as:
const DashboardSwitch = () => {
return (
<Switch>
<Route exact path='/dashboard' component={Home} />
<Route exact path='/dashboard/home' component={Home} />
<Route exact path='/dashboard/bonuses' component={Bonuses} />
</Switch>
);
}
Routing appears to work and the correct components are loaded, however I have noticed that if for example I am at /dashboard and then navigate to /dashboard/bonuses the entire page is reloading including the MyNavbar component. I want the navbar to remain static and only the content below it to reload as I have defined in the Dashboard component.
What am I doing wrong here?
Consider using a layout common to all components or something like this to avoid lose MyNavbar, for example:
const App = () => (
<BrowserRouter>
<Layout>
<Switch>
<Route exact path='/' component={Landing} />
<Route exact path='/login' component={Login} />
<Route exact path='/logout' component={Logout} />
<Route exact path='/dashboard' component={Home} />
<Route exact path='/dashboard/home' component={Home} />
<Route exact path='/dashboard/bonuses' component={Bonuses} />
</Switch>
</Layout>
</BrowserRouter>
)
const Layout = ({ children }) => (
<div>
{children}
<MyNavbar />
</div>
);

How to skip header and footer for certain routes in ReactJS?

I have the following code which renders an app with a header and footer for all pages.
app.js
import React from 'react';
import {
Route,
Switch
} from 'react-router-dom';
import { ConnectedRouter } from 'connected-react-router'
import Layout from './components/Layout'
import Home from './homeComponent';
import Login from './loginComponent';
import Dashboard from './dashboardComponent';
const App = ({ history }) => {
return (
<Layout>
<ConnectedRouter history={history}>
<Switch>
<Route exact={true} path="/" component={Home} />
<Route path="/login" component={Login} />
<Route path="/dashboard" component={Dashboard} />
... more routes
<Route component={NoMatch} />
</Switch>
</ConnectedRouter>
</Layout>
);
};
export default App;
layout.js
import Header from './headerComponent'
import Footer from './footerComponent'
import React, {Component} from 'react'
class Layout extends Component {
render() {
return (
<div>
<Header />
{this.props.children}
<Footer />
</div>
)
}
}
What is the best way to skip rendering of the header and footer for certain pages like Home and Login routes?
I'd recommend creating two layouts with their own header and footers and a private route:
Public Layout
export const PublicLayout = (props) => <div>
<PublicHeader/>
<Switch>
<Route exact path="/" component={HomePage}/>
<Route exact path='/signin' component={SigninForm} />
<Route exact path='/signup' component={Signup} />
</Switch>
<PublicFooter/>
Protected Layout
export const ProtectedLayout = (props) => <div>
<ProtectedHeader/>
<Switch>
<PrivateRoute exact path='/app/dashboard' component={Dashboard} />
<Route component={NotFound} />
</Switch>
<ProtectedFooter/>
Define high-level routes in app.js:
export default () => {
return <div>
<Switch>
<Route path='/app' component={ProtectedLayout} />
<Route path='/' component={PublicLayout} />
</Switch>
</div>
}
Define PrivateRoute:
export default ({component: Component, ...rest}) => (
<Route {...rest} render={props => (
window.globalState.isAuthenticated() ? (
<Component {...props} />
) : (
<Redirect to={{
pathname: '/signin',
state: {from: props.location}
}} />
)
)} />
)
Yeah i know a bit late .
Visual studio 2019
import React from 'react';
import { Container } from 'reactstrap';
import NavMenu from '../components/NavMenu';
export default props => (
<div>
{window.location.pathname !== '/login' ? <NavMenu /> : null}
<Container>
{props.children}
</Container>
</div>
);
i hope somebody helps out there.. !!! Happy coding
I made some solution while solving the problem.
First You can wrap the Switch in a website header and footer
<BrowserRouter>
<WebsiteHeader />
<Switch>
<Route/>
<Route/>
<Route/>
</Switch>
<WebsiteFooter/>
<BrowserRouter>
then inside the header or footer wrap the components using withRouter from 'react-router-dom' so you can access the routes props
const WebsiteHeader = props => {
if (props.location.pathname == "/register") return null;
return (
<Fragment>
<DesktopHeader {...props} />
<MobileHeader {...props} />
</Fragment>
);
};
export default withRouter(WebsiteHeader);
Use render
<ConnectedRouter history={history}>
<Switch>
<Route path="/dashboard" render={props => <Layout><Dashboard {...props} /></Layout>} />
<Route path="/login" component={Login} />
</Switch>
</ConnectedRouter>
For forcefully refresh Header inside routing.
use forceRefresh={true}
const Routing = () => {
return(
<BrowserRouter forceRefresh={true}>
<Header/>
<Switch>
<Route exact path="/" component={Home}/>
<Route path="/list/:id" component={ListingApi}/>
<Route path="/details/:id" component={HotelDetails}/>
<Route path="/booking/:hotel_name" component={PlaceBooking}/>
<Route path="/viewBooking" component={ViewBooking}/>
<Route exact path="/login" component={LoginComponent}/>
<Route path="/signup" component={RegisterComponent}/>
</Switch>
<Footer/>
</BrowserRouter>
)
}
I would just create a few different layouts one with header and footer and one without. And then instead of wrapping everything into one layout. I'd just do this wrapping inside each page component. So your components would be like:
Dashboard component
<SimpleLayout>
<Dashboard>
</SimpleLayout>
Home component
<MainLayout>
<Home>
</MainLayout>
Try like this
<Route path="/" render={(props) => (props.location.pathname !== "/login") &&
<Header />}>
</Route>
<Route path="/" render={(props) => (props.location.pathname !== "/login") &&
<Menu />}>
</Route>
<PrivateRoute path="/scope" component={Scope} ></PrivateRoute>
<Route exact path="/login" component={Login} />
In this example I'm checking the URL, If the URL is "/Login" I'm removing Menu and header component
For forcefully refresh Header inside routing.
const Routing = () => {
return(
<BrowserRouter forceRefresh={true}>
<Header/>
<Switch>
<Route exact path="/" component={Home}/>
<Route path="/list/:id" component={ListingApi}/>
<Route path="/details/:id" component={HotelDetails}/>
<Route path="/booking/:hotel_name" component={PlaceBooking}/>
<Route path="/viewBooking" component={ViewBooking}/>
<Route exact path="/login" component={LoginComponent}/>
<Route path="/signup" component={RegisterComponent}/>
</Switch>
<Footer/>
</BrowserRouter>
)
}

Categories

Resources