Reputation: 7200
I'm building a react app and I can't make the routing work.
I need one common layout (header, footer) for a few Auth routes (/login
, sign-up
, forgot-password
, etc...)
And I need need another common layout for the rest of the protected parts of the app (Home
, Dashboard
, etc...)
I need another 404 page without any layout.
I've tried several techniques from those links:
But could reach working version.
This is what I'm currently have:
(Note: for now I'm ignoring the need to block non logged-in users into the private routes of AppLayout, I'll handle that right after)
const App: React.FC = () => {
const history = createBrowserHistory();
return (
<div className="App">
<Router history={history}>
<Switch>
<AppLayout>
<Route path="/home" component={HomePage}/>
<Route path="/dashboard" component={DashboardPage}/>
...
</AppLayout>
<AuthLayout>
<Route path="/login" component={LoginPage}/>
<Route path="/sign-up" component={SignUpPage}/>
...
</AuthLayout>
<Route path="*" component={NotFoundPage} />
</Switch>
</Router>
</div>
);
};
export default App;
Both AuthLayout
and AppLayout
are simple and similar to that (just with different header/footer for each):
class AppLayout extends Component {
render() {
return (
<div className="AppLayout">
<header>...</header>
{this.props.children}
<footer>...</footer>
</div>
);
}
}
export default AppLayout;
The problem is that only routes from the AppLayout are rendered.
Other routes just showing the AppLayout header
and footer
without any content.
Those are the react versions I'm using:
"react": "^16.8.6",
"react-dom": "^16.8.6",
"react-router-dom": "^5.0.0",
Any help would be appreciated.
Thanks in advance.
Upvotes: 9
Views: 24322
Reputation: 7200
EDIT: I have answered this question with another solution.
Both solutions by @Gaurab Kc & @johnny peter are great though I've ended up doing something like that:
<Router history={history}>
<Switch>
<PrivateRoute
path="/home"
component={HomePage}>
</PrivateRoute>
<PrivateRoute
path="/dashboard"
component={DashboardPage}>
</PrivateRoute>
<AuthRoute
path="/login"
component={LoginPage}>
</AuthRoute>
<AuthRoute
path="/sign-up"
component={SignUpPage}>
</AuthRoute>
<Route path="*" component={NotFoundPage}/>
</Switch>
</Router>
The AuthRoute
& PrivateRoute
are something like that:
interface PrivateRouteProps extends RouteProps {
component: any;
}
const PrivateRoute = (props: PrivateRouteProps) => {
const {component: Component, ...rest} = props;
return (
<Route
{...rest}
render={(routeProps) =>
localStorage.getItem('user') ? (
<div>
... // here is the app header
<Component {...routeProps} />
.. // here is the app footer
</div>
) : (
<Redirect
to={{
pathname: '/login',
state: {from: routeProps.location}
}}
/>
)
}
/>
);
};
export default PrivateRoute;
interface AuthRouteProps extends RouteProps {
component: any;
}
const AuthRoute = (props: AuthRouteProps) => {
const {component: Component, ...rest} = props;
return (
<Route
{...rest}
render={(routeProps) =>
(
<div>
... // here is the auth header
<Component {...routeProps} />
.. // here is the auth footer
</div>
)
}
/>
);
};
export default AuthRoute;
Upvotes: -3
Reputation: 7200
It took me some time to find the answer that I favoured. Both @johnny-peter & @gaurab-kc solutions were great and they tought me about React's routing mechanism.
@johnny-peter 's solution had disadvantage of forcing me to put a prefix for all auth
related routes under /auth/...
(e.g. /auth/login
& auth/sign-up
) which I didn't wanted.
@gaurab-kc solution was supporting only one set of routes.. so if user was already signed up, he couldn't visit the /login
route anymore.
Up till recently I used my own solution which had the problem of "defeating the whole purpose of a common header and footer." as @johnny-peter mentioned and it was down-voted few times as it should be.
Now I'm using another solution:
<Router history={browserHistory}>
<Switch>
<Redirect exact from="/" to="/home"/>
<Route exact path={["/login", "/sign-up", ...]}>
<AuthLayout>
<Switch>
<Route
path="/login"
component={LoginPage}
/>
<Route
path="/sign-up"
component={SignUpPage}
/>
</Switch>
</AuthLayout>
</Route>
<Route exact path={[
"/home",
"/dashboard",
...
]}>
<SiteLayout>
<Switch>
<Route
path="/home"
component={HomePage}
/>
<Route
path="/dashboard"
component={DashboardPage}
/>
</Switch>
</SiteLayout>
</Route>
<Route path="*" component={NotFoundPage}/>
</Switch>
</Router>
which prevents all the above disadventages. It's allows me to:
/login
or other auth
routes without logout first.The only disadvantage of this solution is having more code and duplicating the routes, but it's a cost I'm willing to pay.
Upvotes: 9
Reputation: 4882
Each of your layout should have a path component to differentiate from other layouts.
For example
Auth layouts could reside under /auth
eg, login would /auth/login
, signup would be /auth/signup
App layout could go under /app
eg, dashboard would be /app/dashboard
, home would be /app/home
import { Switch, BrowserRouter, Route, Redirect } from "react-router-dom";
function App() {
return (
<BrowserRouter>
<Layouts />
</BrowserRouter>
);
}
const NotFound = () => <h1>Not Found</h1>;
function Layouts() {
return (
<Switch>
<Route path="/auth" component={AuthLayout} />
<Route path="/app" component={AppLayout} />
<Route path="/" component={NotFound} />
</Switch>
);
}
const Signup = () => <p>Login</p>;
const Login = () => <p>Sign up</p>;
function AuthLayout() {
return (
<div>
<h1>Auth Layout</h1>
<Route path="/auth/signup" exact component={Signup} />
<Route path="/auth/login" exact component={Login} />
<Redirect from="/auth" to="/auth/login" exact />
</div>
);
}
const Home = () => <p>Home</p>;
const Dashboard = () => <p>Dashboard</p>;
function AppLayout() {
return (
<div>
<h1>App Layout</h1>
<Route path="/app/home" exact component={Home} />
<Route path="/app/dashboard" exact component={Dashboard} />
<Redirect from="/app" to="/app/home" exact />
</div>
);
}
Also if you want to protect certain routes from being rendered if not authenticated, then you can create a PrivateRoute
component that would redirect to auth layout if not authenticated.
const PrivateRoute = ({ component: Component, ...rest }) => (
<Route
{...rest}
render={props => sessionStorage.token // your auth mechanism goes here
? <Component {...props} />
: <Redirect to={{ pathname: '/auth' }} />}
/>
);
You can use this PrivateRoute
component instead of react-router
's Route
component.
Eg:
<PrivateRoute path="/app" component={AppLayout} />
Upvotes: 21
Reputation: 851
You could try having two different switch statements to handle your Auth and Protected routes. I had a similar use case at work and having two sets of switch blocks with only one running at one time was the way for me.
const App: React.FC = () => {
const history = createBrowserHistory();
return (
<div className="App">
<Router history={history}>
{isLoggedIn ? <PrivateRoutes /> : <AuthRoutes />}
</Router>
</div>
);
};
const PrivateRoutes: React.FC = () => {
return (
<>
<Header />
<Switch>
<Route path="/home" component={HomePage} />
<Route path="/dashboard" component={DashboardPage} />
<Route path="*" component={NotFoundPage} />
</Switch>
<Footer />
</>
);
};
const AuthRoutes: React.FC = () => {
return (
<>
<Header />
<Switch>
<Route path="/login" component={LoginPage} />
<Route path="/sign-up" component={SignUpPage} />
<Route path="*" component={NotFoundPage} />
</Switch>
<Footer />
</>
);
};
Upvotes: 5