Reputation: 1578
I'd like to render some of my routes within my public layout, and some other routes within my private layout, is there a clean way to do this?
Example that obviously doesn't work, but I hope explains roughly what I'm looking for:
<Router>
<PublicLayout>
<Switch>
<Route exact path="/" component={HomePage} />
<Route exact path="/about" component={AboutPage} />
</Switch>
</PublicLayout>
<PrivateLayout>
<Switch>
<Route exact path="/profile" component={ProfilePage} />
<Route exact path="/dashboard" component={DashboardPage} />
</Switch>
</PrivateLayout>
</Router>
I'd like the layout to switch for certain routes, how do I do this with the new react router?
Nesting routes no longer works and gives me this error:
You should not use <Route component> and <Route children> in the same route; <Route children> will be ignored
Edit: Having layouts wrap entire groups of routes also means those layouts are only rendered once as long as you stay in the same private/public group of routes. This is a big deal if your layout has to fetch something from your server for example, as that would happen on every page change if you wrap each page with a layout.
Upvotes: 50
Views: 33198
Reputation: 4230
UPDATE 2023
In react router 6, I'm using this approach now:
const Pages = () => (
<BrowserRouter>
<Routes>
<Route element={<GeneralLayout />}>
<Route path="/" element={<Home />} />
<Route path="/analysis" element={<Home />} />
<Route path="/account/profile" element={<Profile />} />
<Route path="/account/settings" element={<Settings />} />
</Route>
</Routes>
</BrowserRouter>
);
UPDATE 2020
Well for now I'm following this approach, it's simpler that the one I posted before:
const Pages = () => {
return (
<ReactRouter>
<Switch>
<Route path="/comingsoon" component={ComingSoon} exact />
<Route>
<MainLayout>
<Switch>
<Route path="/home" exact>
<Home />
</Route>
<Route path="/login" exact>
<Login />
</Route>
<Route path="/useraccount" exact>
<UserAccount />
</Route>
<Route path="/createaccount" exact>
<CreateAccount />
</Route>
<Route path="/contact" exact>
<Contact />
</Route>
<Route path="/about" exact>
<About />
</Route>
<Redirect path="/" exact to="/comingsoon" />
<Route path="*" exact component={NotFound} />
</Switch>
</MainLayout>
</Route>
</Switch>
</ReactRouter>
);
};
In this way, the MainLayout will take care of everything except for the coming soon page.
OLD ANSWER
If you are using Typescript and want to follow this react layout aproach then you can declare your layout like this:
import './Default.less';
import React from 'react';
import { Route } from "react-router-dom";
import { Sider } from './Components';
import { Notification } from 'Client/Components';
interface IDefaultProps {
component: any
path?: string;
exact?: boolean;
}
const Default: React.SFC<IDefaultProps> = (props) => {
const { component: Component, ...rest } = props;
return <Route {...rest} render={matchProps => (
<div className="defaultLayout">
<Sider />
<div className="defaultLayoutContent">
<Component {...matchProps} />
</div>
<Notification />
</div>
)} />
}
export default Default;
And declare routes like this:
import React from 'react';
import { Route } from 'react-router-dom';
import { DefaultLayout } from 'Client/Layout';
import { Dashboard, Matters, Schedules, Students } from './Containers';
export const routes = <div>
<DefaultLayout exact path="/" component={Dashboard} />
<DefaultLayout path="/matters" component={Matters} />
<DefaultLayout path="/schedules" component={Schedules} />
<DefaultLayout path="/students" component={Students} />
</div>;
Upvotes: 21
Reputation: 11067
After looking for it, the clean and efficient way (avoiding abusive re-rendering):
<Route exact path={["/about", "/"]}>
<PublicLayout>
<Route exact path="/" component={HomePage} />
<Route path="/about" component={AboutPage} />
</PublicLayout>
</Route>
<Route path={["/profile", "/dashboard"]}>
<PrivateLayout>
<Route path="/profile" component={ProfilePage} />
<Route path="/dashboard" component={DashboardPage} />
</PrivateLayout>
</Route>
aslo, It can be refactored, see my complete answer: https://stackoverflow.com/a/57358661/3437790
Upvotes: 17
Reputation: 769
Use the render prop of Route component, this will not unmount and mount your layout component on every route change. More details of how this works here.
Say you have two layout components Primary.js and Secondary.js
In your App.js render method, you simply return
<BrowserRouter>
<Switch>
<Route path='/login' render={() => <Secondary><Login/></Secondary>} />
<Route path='/dashboard' render={() => <Primary><Dashboard/></Primary>} />
</Switch>
</BrowserRouter>
To refine it further, you can also define a Higher order component layout component to wrap your page component with a layout. (Not tested)
<Route to='/login' render={() => Secondary(Login)}
Upvotes: 1
Reputation: 101
@Qop is correct, however in the new React Router I noticed if you have your root path inside of the switch as the first route it will always match to the it and therefore never display your following routes. You should put the root path at the end.
<Switch>
<RouteWithLayout layout={PublicLayout} path="/about" component={AboutPage}/>
<RouteWithLayout layout={PrivateLayout} path="/profile" component={ProfilePage}/>
<RouteWithLayout layout={PrivateLayout} path="/dashboard" component={DashboardPage}/>
<RouteWithLayout layout={PublicLayout} path="/" component={HomePage}/>
</Switch>
Upvotes: -1
Reputation: 379
This solution will work.
<Router>
<Switch>
<PublicLayout>
<Route exact path="/" component={HomePage} />
<Route exact path="/about" component={AboutPage} />
</PublicLayout>
</Switch>
<Switch>
<PrivateLayout>
<Route exact path="/profile" component={ProfilePage} />
<Route exact path="/dashboard" component={DashboardPage} />
</PrivateLayout>
</Switch>
</Router>
Upvotes: -5
Reputation: 980
Same idea with @Zaptree
Layout
function PublicLayout(props) {
return (
<Route {...props} />
);
}
function PrivateLayout(props) {
return (
<Route {...props} />
);
}
Routes
<Switch>
<PublicLayout exact path="/" component={HomePage} />
<PrivateLayout path="/profile" component={ProfilePage} />
<Route path="/callback" component={NoLayoutPage} />
</Switch>
Upvotes: -2
Reputation: 943
I am going to write this everywhere where this question was asked so sorry if you've seen it elsewhere. I am only doing it cause I've struggled for a while and I think it's worth spreading this solution as much as possible. Enough words, this is what I did, I think it's pretty self explanatory. Works like a charm and it's very easy to type.
const withLayout = (LayoutComp, ComponentComp) => <LayoutComp><ComponentComp /></LayoutComp>;
const withDefaultLayout = (ComponentComp) => () => withLayout(Layout, ComponentComp);
const withEmptyLayout = (ComponentComp) => () => withLayout(EmptyLayout, ComponentComp);
export const routes = <div>
<Switch>
<Route path="/" exact render={withDefaultLayout(Home)} />
<Route path='/subscriptions' render={withDefaultLayout(SubscriptionsWrapped)} />
<Route path='/callback' render={withEmptyLayout(AuthCallback)} />
<Route path='/welcome/initial' render={withEmptyLayout(Start)} />
</Switch>
</div>;
Upvotes: 5
Reputation: 9528
The idea is the same as that of Zaptree's but using es6 syntax and added checks so it can be used in place of react-router's Route component
Create a new component, say /src/components/Route/index.js:
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import {Route as ReactRoute} from 'react-router'
class Route extends Component {
static propTypes = {
component: PropTypes.func.isRequired,
layout: PropTypes.func,
path: PropTypes.string,
exact: PropTypes.bool
}
render = () => {
const {component, layout, path, exact} = this.props
let routeComponent = props => React.createElement(component, props)
if (layout) {
routeComponent = props =>
React.createElement(layout, props, React.createElement(component, props))
}
return <ReactRoute path={path} exact={exact} render={routeComponent}/>
}
}
export default Route
Use the created Route component:
import Route from 'components/Route/'
...
<Router history={createHistory()}>
<Switch>
<Route exact path='/' layout={PublicLayout} component={HomePage}/>
<Route exact path='/' layout={PrivateLayout} component={ProfilePage}/>
<Route path='/logins' component={Login}/>
</Switch>
</Router>
Upvotes: 1
Reputation: 5883
I don't think layouts belong in the route files.
Keep the route clean, ie:
<Route exact path="/" component="HomePage" />
Then, in the HomePage
component, wrap the rendered content in your layout of choice:
...
render() {
<PublicLayout>
<h1>Home page!</h1>
</PublicLayout>
}
This way the routes remain super clean, plus you have an easy way to display routes which should support both layouts (404 pages for instance).
Upvotes: 6
Reputation: 3803
What I have done for this is create a simple component that adds an extra property to the Route component which is layout:
function RouteWithLayout({layout, component, ...rest}){
return (
<Route {...rest} render={(props) =>
React.createElement( layout, props, React.createElement(component, props))
}/>
);
}
Then in your case your routes would look like this
<Switch>
<RouteWithLayout layout={PublicLayout} path="/" component={HomePage}/>
<RouteWithLayout layout={PublicLayout} path="/about" component={AboutPage}/>
<RouteWithLayout layout={PrivateLayout} path="/profile" component={ProfilePage}/>
<RouteWithLayout layout={PrivateLayout} path="/dashboard" component={DashboardPage}/>
</Switch>
Upvotes: 34
Reputation: 1578
Update: I solved it another way, but if forces you to namespace the different parts of your app with /app or /admin for example.
Each of the components UserRoutes, AdminRoutes and PublicRoutes are basically large Switch components with the specific layout at its root.
Here's how it looks:
<Router>
<Switch>
<Route path="/app" render={props => <UserRoutes {...props} />} />
<Route path="/admin" render={props => <AdminRoutes {...props} />} />
<Route path="/" render={props => <PublicRoutes {...props} />} />
</Switch>
</Router>
Old: One solution would be to use the render prop of each Route, but it seems really cumbersome:
<Router>
<Switch>
<Route
path="/"
render={() => <PublicLayout><HomePage /></PublicLayout>}
/>
<Route
path="/about"
render={() => <PublicLayout><AboutPage /></PublicLayout>}
/>
<Route
path="/profile"
render={() => <PrivateLayout><ProfilePage /></PrivateLayout>}
/>
<Route
path="/dashboard"
render={() => <PrivateLayout><DashboardPage /></PrivateLayout>}
/>
</Switch>
</Router>
Upvotes: 1
Reputation: 61
I tried Florians answer but that wasn't enough for me as react will create a separate instance of your Layout for each route, hence any navigation transitions like a tab sliding will be killed.
I'm hoping for a more elegant solution, but this has got my app working again with v4.
Define one Route pointing to a component that will decide what to wrap the route in
<Router>
<Route component={AppWrap} />
</Router>
In AppWrap do something like the following
var isPrivateLayout;
for (var path of listOfPrivateLayoutPaths) {
if (this.props.location.pathname == path) {
isPrivateLayout= true;
}
}
if (isPrivateLayout) {
return <PrivateLayout>
(routes)
</PrivatelyLayout>
} else {
return <PublicLayout>
(routes)
</PublicLayout>;
}
Route Config maybe could be used for a cleaner representation of this, not sure.
Upvotes: 2