manish thakur
manish thakur

Reputation: 820

How to dynamically use react routing

I have a application where I have two logins one for superAdmin and other for 'Admin'.

  1. I have several pages One which is common (home page) both users have excess to that.

  2. then I have several other pages which are some for admin and others are for superAdmin.

  3. Now when I open my page I am trying to go '/' this route (My home route). What I am trying to do

  4. Now If I am logged in as admin and admin user is typing some superAdmin url in address bar I want that to be redirect to current admin route

  5. Same goes for superAdmin as well

  6. both the user I want to restrict to excess each-others route

  7. And if I am admin user or superAdmin user and trying to logged in and trying to excess authenticated route I should be redirect to home page

What I have done

I have created one component (Dynamic route) here I am checking what user is trying to do. and in route.js in my routing file I am passing props as guest,superAdmin and admin

Dynamicroute.js code

I have created my context to store the user once they logged in

    export default function Dynamicroute(props) {
    const { user } = useAuthState();  // this I am getting from my context
    console.log(user);

    if (props.partner && !user) {
        console.log('admin not logedin');
        return <Redirect to="/admin" />;
    } else if (props.admin && !user) {
        console.log('superAdmin not loged in');
        return <Redirect to="/superAdmin" />;
    } else if (props.admin && user.role === 'admin') {
        console.log('admin logedin');
        return <Redirect to="/admin_home" />;
    } else if (props.admin && user.role === 'superAdmin') {
        console.log('super admin loged in');
        return <Redirect to="/superadmin_home" />;
    } else if (props.guest && user) {
        console.log('guest');
        return <Redirect to="/" />;
    } else {
        return <Route component={props.component} {...props} />;
    }
}

My route.js

<DuynamicRoute exact path="/" component={Home} guest />
<DuynamicRoute path="/admin" component={loginAdmin} guest />
<DuynamicRoute path="/superAdmin" component={loginSuperAdmin} guest />
<DuynamicRoute path="/admin_home" component={admin_home} admin/>
<DuynamicRoute path="/superAdmin_home" component={superAdmin_home} superAdmin/>

Issue I am facing

I don't know what issue I am facing it is redirecting me to that route on login but content is not loading If I console something on that page I am not able to get that, the page is getting blank.

I am following this lecture from 25:00 timing

I am getting this blank page

Edited

Here is my code sand box

Please do check this

Edit

admin and super admin are going to be loged in different browsers, So just do not want admin to access super admin and vice-versa if they type in url each other's rout

Upvotes: 8

Views: 3766

Answers (4)

Jacob Smit
Jacob Smit

Reputation: 2379

As this doesn't have an accepted answer yet I'll throw in the simple approach I use for adding / removing routes based on a condition.

The examples use Typescript but it should be relatively easy to strip the types out if needed.

Conditional Route Component:

This component has full typing of all the Route props plus an added prop called enabled.

When enabled is true the route will be rendered as normal, when it is false null will be returned.

// ConditionalRoute.tsx
import * as React from 'react';
import { Route, RouteProps } from 'react-router-dom';

interface ConditionalRouteProps extends RouteProps {
    enabled?: boolean;
}

const ConditionalRoute: React.FunctionComponent<ConditionalRouteProps> = ({
    enabled,
    ...routeProps
}) => {
    if (!enabled) {
        return null;
    }

    return (
        <Route {...routeProps} />
    );
};

export default ConditionalRoute;

Conditional Redirect Component:

This component has full typing of all the Redirect props plus an added prop called enabled.

When enabled is true the redirect will be actioned as normal, when it is false null will be returned.

// ConditionalRedirect.tsx
import * as React from 'react';
import { Redirect, RedirectProps } from 'react-router-dom';

interface ConditionalRedirectProps extends RedirectProps {
    enabled?: boolean;
}

const ConditionalRedirect: React.FunctionComponent<ConditionalRedirectProps> = ({
    enabled,
    ...redirectProps
}) => {
    if (!enabled) {
        return null;
    }

    return (
        <Redirect {...redirectProps} />
    );
};

export default ConditionalRedirect;

Using the Conditional Components:

Use the Conditional Routes / Redirects as you would their original base components except they will not come into effect unless the enabled property is true.

This also works with the Switch component as it will ignore components that return null.

// App.tsx
import * as React from 'react';
import { BrowserRouter, Switch } from 'react-router-dom';

interface AppProps {
    authenticatedUser?: any;
}

const ConditionalRedirect: React.FunctionComponent<AppProps> = ({
    authenticatedUser
}) => {

    return (
        <BrowserRouter>
            <Switch>
                { 
                    // Always allow the unauthenticated error route
                }
                <Route 
                    path="/error/401" 
                    component={Unauthenticated} 
                />
                { 
                    // If user is authenticated allow member route
                }
                <ConditionalRoute 
                    enabled={!!authenticatedUser} 
                    path="/member" 
                    component={MemberHome} 
                />
                { 
                    // If user is an admin allow admin route
                }
                <ConditionalRoute 
                    enabled={authenticatedUser?.isAdmin} 
                    path="/administration" component={AdminHome} 
                />
                { 
                    // If user is an admin redirect uncaught route 
                    // to administration route
                }
                <ConditionalRedirect 
                    enabled={authenticatedUser?.isAdmin} 
                    path="/" 
                    to="/administration" 
                />
                { 
                    // If user is authenticated redirect uncaught route 
                    // to member route
                }
                <ConditionalRedirect 
                    enabled={!!authenticatedUser} 
                    path="/" 
                    to="/member" 
                />
                { 
                    // If user is not authenticated redirect uncaught 
                    // route to unauthenticated error route 
                }
                <ConditionalRedirect 
                    enabled={!authenticatedUser} 
                    path="/" 
                    to="/error/401" 
                />
            </Switch>
        </BrowserRouter>
    );
};

export default App;

The main caveat of this solution is that any value in a condition has to be ready to be evaluated before rendering any of the routes. For example if you make an async request to check if a user is authenticated in the example App above, when the user is initially undefined before the async request returns it will redirect you to the 401 page.

Upvotes: 0

Mohammad Oftadeh
Mohammad Oftadeh

Reputation: 1449

For better management and development of the program along with the best practices, Do the Authorization in React.js as follows:

Demo on Codesandbox

First: You need a class for checking permissions and routes/pages configs like below:

class AppUtils {
  static setRoutes(config) {
    let routes = [...config.routes];

    if (config.auth) {
      routes = routes.map((route) => {
        let auth = config.auth ? [...config.auth] : null;
        auth = route.auth ? [...auth, ...route.auth] : auth;
        return {
          ...route,
          auth
        };
      });
    }

    return [...routes];
  }

  static generateRoutesFromConfigs(configs) {
    let allRoutes = [];
    configs.forEach((config) => {
      allRoutes = [...allRoutes, ...this.setRoutes(config)];
    });
    return allRoutes;
  }

  static hasPermission(authArr, userRole) {
    /**
     * If auth array is not defined
     * Pass and allow
     */
    if (authArr === null || authArr === undefined) {
      // console.info("auth is null || undefined:", authArr);
      return true;
    } else if (authArr.length === 0) {
      /**
       * if auth array is empty means,
       * allow only user role is guest (null or empty[])
       */
      // console.info("auth is empty[]:", authArr);
      return !userRole || userRole.length === 0;
    } else {
      /**
       * Check if user has grants
       */
      // console.info("auth arr:", authArr);
      /*
            Check if user role is array,
            */
      if (userRole && Array.isArray(userRole)) {
        return authArr.some((r) => userRole.indexOf(r) >= 0);
      }

      /*
            Check if user role is string,
            */
      return authArr.includes(userRole);
    }
  }
}

export default AppUtils;

Second: You need Authorization component for handling routes like below:

import React, { Component } from "react";
import AppUtils from "utils/AppUtils";
import { matchRoutes } from "react-router-config";
import { withRouter } from "react-router-dom";
import AppContext from "context/AppContext";

class AppAuthorization extends Component {
  constructor(props, context) {
    super(props);
    const { routes } = context;
    this.state = {
      accessGranted: true,
      routes
    };
  }

  componentDidMount() {
    if (!this.state.accessGranted) {
      this.redirectRoute();
    }
  }

  componentDidUpdate() {
    if (!this.state.accessGranted) {
      this.redirectRoute();
    }
  }

  static getDerivedStateFromProps(props, state) {
    const { location, userRole } = props;
    const { pathname } = location;

    const matched = matchRoutes(state.routes, pathname)[0];

    return {
      accessGranted: matched
        ? AppUtils.hasPermission(matched.route.auth, userRole)
        : true
    };
  }

  shouldComponentUpdate(nextProps, nextState) {
    return nextState.accessGranted !== this.state.accessGranted;
  }

  redirectRoute() {
    const { location, userRole, history } = this.props;
    const { pathname, state } = location;
    const redirectUrl = state && state.redirectUrl ? state.redirectUrl : "/";

    /*
        User is guest
        Redirect to Login Page
        */
    if (!userRole || userRole.length === 0) {
      history.push({
        pathname: "/login",
        state: { redirectUrl: pathname }
      });
    } else {
      /*
        User is member
        User must be on unAuthorized page or just logged in
        Redirect to dashboard or redirectUrl
        */
      history.push({
        pathname: redirectUrl
      });
    }
  }

  render() {
    // console.info('App Authorization rendered', accessGranted);
    return this.state.accessGranted ? (
      <React.Fragment>{this.props.children}</React.Fragment>
    ) : null;
  }
}

// AppAuthorization.defaultProps = {
//   userRole: [] // You can manage roles by redux or any state managements
// };

AppAuthorization.contextType = AppContext;

export default withRouter(AppAuthorization);

Third: You need authRoles file or remote for managing roles on client like below:

/**
 * Authorization Roles
 */
const authRoles = {
  admin: ["admin"],
  superAdmin: ["superAdmin"],
  user: ["user"],
  onlyGuest: []
};

export default authRoles;

Forth: If you want to move forward with this logic, you have to implement the structure of your pages as follows:

src
 |---pages
      |---home
           |---HomePage.jsx
           |---HomePageConfig.jsx
      |
      |- The rest of the pages should be implemented in the same way

For example: When you want to implement a page that only the admin can see (admin home page config):

import React from "react";
import authRoles from "auth/authRoles";

export const AdminHomePageConfig = {
  auth: authRoles.admin,
  routes: [
    {
      path: "/admin",
      exact: true,
      component: React.lazy(() => import("./HomePage"))
    }
  ]
};

Or the home page that everyone can see:

import React from "react";

export const HomePageConfig = {
  routes: [
    {
      path: "/",
      exact: true,
      component: React.lazy(() => import("./HomePage"))
    }
  ]
};

According to the example above, you can enter the auth prop with the role here, to restrict access to the page.

To get a closer look at this logic, I implemented it in the Codesandbox, which you can see via the link below:

Demo on Codesandbox

Notice: The above demo needs to be more complete, and instead of storing user roles in the state, it is better to use state management packages (redux, ...) and also perform login operations through cookies.

Upvotes: 4

deckele
deckele

Reputation: 4893

The problem is that the DuynamicRoute component returns a Redirect in its top level, but Redirects don't work directly inside a Switch components. This is because a Redirect in a Switch would lead to an infinite redirection loop. To fix this, you should return a top level Route from your custom Route component, while handling the redirection logic between the Route tags.

Also, it's worth mentioning that some routes shouldn't be special protected routes, but regular landing pages, such as the home and login pages.

Here is a sample project based on your CodeSandbox solution: https://codesandbox.io/s/vigilant-feather-jbq4j

I made it so that superAdmin user can access admin level, but not the other way around. A lesser admin cannot access superAdmin content, without changing the active user to a superAdmin.

Here is an additional link with a slight modification to logic, for the use case where you would prefer that admin and superAdmin can't access each-other's protected pages: https://codesandbox.io/s/brave-haze-zsmn9?file=/src/ProtectedRoute.js

Upvotes: 0

andrewgi
andrewgi

Reputation: 632

Instead of creating a dynamic route, you can create a function that checks auth and redirects on entering the route.

const yourRouter = () => {
  // yourAuthLogic
  const routeAuth = (Component, props) => {
  // redirect logic
  // here you use the if/else branching based on auth state to redirect  
  // if no redirect
  return (
    <Component {...props} />
  )
}

return (
  <Router>
    <Switch>
      <Route path="/admin" render={() => routeAuth(component, props)} />
      <Route path="/superAdmin" render={() => routeAuth(component, props)} />
   </Switch>
 </Router>
  )
}

Upvotes: 0

Related Questions