Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React Router v5.0 Nested Routes

I'm building a react app and I can't make the routing work.

  1. I need one common layout (header, footer) for a few Auth routes (/login, sign-up, forgot-password, etc...)

  2. And I need need another common layout for the rest of the protected parts of the app (Home, Dashboard, etc...)

  3. I need another 404 page without any layout.

I've tried several techniques from those links:

  • Multiple Layouts with React Router v4
  • https://simonsmith.io/reusing-layouts-in-react-router-4
  • Nested routes with react router v4 / v5
  • https://reacttraining.com/react-router/web/example/route-config

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.

like image 321
ET-CS Avatar asked Jun 22 '19 00:06

ET-CS


People also ask

Can we use nested routes in react?

js application to implement nested routing in the current version of react-router that is React Router DOM version 6. We use nested routing in our application so that a parent component has control over its child component at the route level.

Can we use nested switch in react router?

Set up Nested Tabs You do not need to configure static routes on top of tabs as React Router allows you to set up routes basically everywhere. Set up your path route to map to your to links. Note that the exact prop on the first route indicates a rigorous match.

Is react router deprecated?

This feature has been deprecated because the new structure of Routes is that they should act like components, so you should take advantage of component lifecycle methods instead.

What can I use instead of useHistory?

The useNavigate() hook is introduced in the React Router v6 to replace the useHistory() hook.


2 Answers

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

Working Demo

Edit hungry-dubinsky-q1l62

App.js

import { Switch, BrowserRouter, Route, Redirect } from "react-router-dom";

function App() {
  return (
    <BrowserRouter>
      <Layouts />
    </BrowserRouter>
  );
}

Layouts.js

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>
  );
}

AuthLayout

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>
  );
}

AppLayout

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.

PrivateRoute.js

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} />

like image 189
johnny peter Avatar answered Sep 28 '22 08:09

johnny peter


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:

  1. Use a layout for each section which isn't re-rendered on route change.
  2. Doesn't forcing me to add any prefix to routes.
  3. All routes are working at the same time, letting users to route back into the /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.

like image 25
ET-CS Avatar answered Sep 28 '22 06:09

ET-CS