1

Im trying to figure out how to structure a Router to use different routes for admin, user and public.

I have seen this post and the answer describing a key cloak - but I haven't been able to make sense of it.

I've seen this code sandbox which looks logical to me, but I'm having trouble incorporating it.

I have a constants file where I and defining routes as:

export const NEWBLOG = '/admin/newblog';
export const VIEWBLOG = '/viewblog';

I'm importing that into my App.js and then wanting to define different consts for Admin, User and Public as follows:

import * as ROUTES from '../../util/constants/Routes';
import NewBlog from '../../components/blog/admin/New';

// admin routes
const Admin = ({ match }) => (
  <React.Fragment>
    <Route path={`${match.path}/${ROUTES.NEWBLOG}`} component={NewBlog} />
    <Route path={`${match.path}/2`} render={() => <h2>test</h2>} />
  </React.Fragment>
);

// authenticated user routes
const Other = ({ match }) => (
  <React.Fragment>
    <Switch>
      <Route path={`${match.path}/2`} render={() => <h2>one</h2>} />
      <Route path={`${match.path}/2`} render={() => <h2>two</h2>} />
    </Switch>
  </React.Fragment>
);

// public routes
const Public = ({ match }) => (
  <React.Fragment>
    <Switch>
      <Route path={`${match.path}/2`} render={() => <h2>one</h2>} />
      <Route path={`${match.path}/2`} render={() => <h2>two</h2>} />
    </Switch>
  </React.Fragment>
);

Then inside the router statement I have:

const App = () => (

    <Router>
        <Navigation />
        <Switch>

            <Route path="/a" component={Admin} />
            <Route path="/u" component={Other} />
            <Route path="/p" component={Public} />

            <Route
                component={({ location }) => {
                  return (
                    <div
                      style={{
                        padding: "50px",
                        width: "100%",
                        textAlign: "center"
                      }}
                    >
                      <ErrorMessage />
                    </div>
                  );
                }}
              />
        </Switch>

    </Router>

);

export default App;

This all works until I try to use the routes constants inside the back ticks part of the Admin constant.

I can't seem to use that approach.

Can anyone help with a source of reference materials to find a way through this?

Mel
  • 2,481
  • 26
  • 113
  • 273
  • How it doesn't work? Why don't you define just the specific route part for the `NEWBLOG` constant like `const NEWBLOG = 'newblog'` instead of `const NEWBLOG = '/admin/newblog'`? – Christos Lytras Apr 18 '20 at 05:43
  • I am defining like that in the code, but just trying to make it clearer in the example. The part that stops it working is the ROUTES.NEWBLOG inside the back ticks – Mel Apr 18 '20 at 06:40
  • It works, you can check it [here](https://codesandbox.io/s/react-const-routes-vke31) and it has `ROUTES.NEWBLOG` inside curly brackets string literal. The issue I see is that you define the whole route in the `NEWBLOG` variable like `/admin/newblog`. – Christos Lytras Apr 18 '20 at 06:51

1 Answers1

4

There are few things you need to know

  1. Child Routes will render only when the Parent route path is matched
  2. For the Child Route the path needs to be the path that matched the parent + the child route path
  3. You can write wrappers over route which are responsible for deciding if the user is authenticated or an admin
  4. In all such scenarios you need to store the user authentication state within state, context or redux store.
  5. When you render the Route in Admin like
<Route path={`${match.path}/${ROUTES.NEWBLOG}`} component={NewBlog} />

The path to the component actually becomes /a/admin/newBlog which is actually incorrect

Overall you can change your code to something like this

App.js

const App = () => (
    <Router>
        <Navigation />
        <Switch>
            <Route path="/admin" component={Admin} />
            <Route path="/user" component={Other} />
            <Route path="/public" component={Public} />
        </Switch>
    </Router>
);

AuthRoute.js

const AuthRoute = (props) => {
   const {path, match, component: Component, render, ...rest} = props;
   const {user, isLoading} = useContext(AuthContext); // Assuming you use context to store route, you can actually get this values from redux store too.
   return (
      <Route
          {...rest}
          path={`${match.path}${path}`}
          render={(routerProps) => {
               if(isLoading) return <div>Loading...</div>
                if(!user) return <div>Not Authenticated</div>
               return Component? <Component {...rest} {...routerProps} /> : render(routerProps)
          }}
      />

}

An adminRoute needs to both check whether the user is admin as well as check if he is authenticated or not so you component would look like

AdminRoute.js

const AdminRoute = (props) => {
   const {path, match, ...rest} = props;
   const {user, isLoading} = useContext(AuthContext); // Assuming you use context to store route, you can actually get this values from redux store too.
   return (
      <Route
          {...rest}
          path={`${match.path}${path}`}
          render={(routerProps) => {
               if(isLoading) return <div>Loading...</div>
               if(!user) return <div>Not Authenticated</div>
               if(user.role !== "admin") return <div>Need to be an admin to access this route</div>
               return Component? <Component {...rest} {...routerProps} /> : render(routerProps)
          }}
      />

}

Now you can use the above two components to separate out the Admin and Auth Routes

Also keep in mind that AuthRoutes and public routes paths cannot be the same

Route constants

export const NEWBLOG = '/newblog';
export const VIEWBLOG = '/viewblog';

Routes

import * as ROUTES from '../../util/constants/Routes';
import NewBlog from '../../components/blog/admin/New';

// admin routes
const Admin = (props) => (
  <React.Fragment>
    <AdminRoute {...props} path={ROUTES.NEWBLOG} component={NewBlog} />
    <AdminRoute {...props} path='/2' render={() => <h2>test</h2>} />
  </React.Fragment>
);

// authenticated user routes
const Other = (props) => (
  <React.Fragment>
    <Switch>
      <AuthRoute {...props} path={'/3'} render={() => <h2>one</h2>} />
      <AuthRoute {...props} path={'/4'} render={() => <h2>two</h2>} />
    </Switch>
  </React.Fragment>
);

// public routes
const Public = ({ match }) => (
  <React.Fragment>
    <Switch>
      <Route path={`${match.path}/5`} render={() => <h2>one</h2>} />
      <Route path={`${match.path}/6`} render={() => <h2>two</h2>} />
    </Switch>
  </React.Fragment>
);
Jagrati
  • 11,474
  • 9
  • 35
  • 56
  • Thank you very much for this. I'll try it in the morning. Meanwhile, I'd be really grateful if you could give me a reference to look into to try to understand what: 1. component: Component 2. const {path, match, ...rest} = props; and 3. – Mel Apr 18 '20 at 10:14
  • 1,2 are concepts of object destructing and rest parameters. In one you destructure from an object and assign a different variable name to it. Second is the rest spread syntax wherein ...rest means all the rest elements i nthe props. In the third case we are actually passing all the rest parms to the Route component. Refer MDN for [Rest](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/rest_parameters) and [Object Destructuring](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment#Object_destructuring) here – Jagrati Apr 18 '20 at 10:28
  • Thank you for this help. I'm still trying to make sense of these lines. I'm really slow to learn and have a lot of trouble making sense of documentation. I'm slower than the bounty period allows, so I've given the points and I'll come back to accept this answer when I have worked this through. – Mel Apr 24 '20 at 21:24