DEV Community

Cover image for Basic Routing & Auth in React
Brama Udi
Brama Udi

Posted on

Basic Routing & Auth in React

In this articles i'm using CRA (create-react-app) to create React project.

npm init react-app ProjectName
# or
npx create-react-app ProjectName
# if using yarn
yarn create react-app ProjectName
Enter fullscreen mode Exit fullscreen mode

Install Dependency

The project created by CRA is just only contains React core so if we want to do routing we'll need to install a package called react-router-dom.

npm i react-router-dom
# or
yarn add react-router-dom
Enter fullscreen mode Exit fullscreen mode

Router Configuration

I'm usually make a seperate folder called router to handle all my routing files, so let create this inside our src dir.

After that we will create an index.js file inside router folder then paste this;

// src/router/index.js
import React, { Suspense, lazy } from 'react'
import { Switch, Route } from 'react-router-dom'

// navigation guard / middleware
import { PrivateRoute } from './_private'
import { GuestRoute } from './_guest'

// Preloading component
const Loading = () => <h1>Loading ...</h1>

// Import helper func
const views = (path) => {
    return lazy(() => import(`../views/${path}`))
}

// route list
const routes = [
  {
    path: '/',
    component: 'index'
  },
  {
    path: '/login',
    component: 'login',
    guest: true
  },
  {
    path: '/secret',
    component: 'secret',
    private: true
  },
  {
    path: '404', // 404 fallback
    noExact: true, // all route "exact" by default
    component: '404'
  }
]

const router = () => (
  <Suspense fallback={<Loading />}>
    <Switch>
      {routes.map((route, index) => {
        if (route.path !== '404') {
          if (route.private) {
            return route.noExact ?
            <PrivateRoute key={index} path={route.path} component={views(route.component)} /> :
            <PrivateRoute key={index} exact path={route.path} component={views(route.component)} />
          }
          else if (route.guest) {
            return route.noExact ?
            <GuestRoute key={index} path={route.path} component={views(route.component)} /> :
            <GuestRoute key={index} exact path={route.path} component={views(route.component)} />
          }
          else {
            return route.noExact ?
            <Route key={index} path={route.path} component={views(route.component)} /> :
            <Route key={index} exact path={route.path} component={views(route.component)} />
          }
        } else {
          return <Route key={index} component={views(route.component)} />
        }
      })}
    </Switch>
  </Suspense>
)

export default router
Enter fullscreen mode Exit fullscreen mode

In the code above we importing Suspense and lazy module, this is useful for lazy loading / code-splitting our React component for performant purpose and it also will displaying a loading placeholder component at loading time.

With the configuration like that we will have something like route rules like this;

Prop. Name Required Value Info
path String Router URL
component String Views component
private × Boolean Only logged user can access
guest × Boolean Only guest can access
noExact × Boolean All routes all exact by default, so it can be optional for specified use

To make the navigation guard work as expected we have to make a middleware for the private route and guest-only route.

So create a file named _private.js;

// src/router/_private.js
import React from 'react'
import { Route, Redirect } from 'react-router-dom'
import Auth from '../store/auth'

const FALLBACK = '/login'

export const PrivateRoute = ({ component: Component, ...rest}) => {
    return (
        <Route
            {...rest}
            render={props => {
                if (Auth.state.logged) {
                    return <Component {...props} />
                }
                else {
                    return (
                        <Redirect to={{
                                pathname: FALLBACK,
                                state: {
                                    from: props.location
                                }
                            }}
                        />
                    )
                }
            }}
        />
    )
}
Enter fullscreen mode Exit fullscreen mode

and create file named _guest.js too then paste the code above but with these following changes;

- if (Auth.state.logged) {
+ if (!Auth.state.logged) {
Enter fullscreen mode Exit fullscreen mode
- const FALLBACK = '/login'
+ const FALLBACK = '/secret'
Enter fullscreen mode Exit fullscreen mode
- export const PrivateRoute = ({ component: Component, ...rest}) => {
+ export const GuestRoute = ({ component: Component, ...rest}) => {
Enter fullscreen mode Exit fullscreen mode

Store Configuration

Here the store are plain javascript where used to handle auth state variable that will be global state.

I'm personally use folder named store to store all my "state-handle file", do the same thing with auth.js;

// src/store/auth.js
const state = {
    logged: !!localStorage.getItem('token')
}

const actions = {
    login: () => {
        return new Promise((resolve, reject) => {
            localStorage.setItem('token', JSON.stringify({
        // In real world token is obtained from api request
                token: 'abcdef321654'
            }))
            resolve()
        })
    },

    logout: () => {
        return new Promise((resolve, reject) => {
            localStorage.removeItem('token')
            resolve()
        })
    }
}

export default { state, actions }
Enter fullscreen mode Exit fullscreen mode

Views Component

Now we should write the views components where these will be a our pages display, all these files are located in views dir;

index.js

import React from 'react'

const Index = () => <h1>Index Page</h1>

export default Index
Enter fullscreen mode Exit fullscreen mode

login.js

import React from 'react'
import Auth from '../store/auth'

const handleLogin = (props) => {
    Auth.actions.login().then(
        () => {
            props.history.push('/secret')
        }
    )
    .catch(
        () => alert('Login failed')
    )
}

const Login = (props) => (
    <div>
        <h1>Login Page</h1>
        <button onClick={() => handleLogin(props)}>Login</button>
    </div>
)

export default Login
Enter fullscreen mode Exit fullscreen mode

secret.js

import React from 'react'
import Auth from '../store/auth'

const handleLogout = (props) => {
    Auth.actions.logout().then(
        () => {
            props.history.push('/login')
        }
    )
}

const Secret = (props) => (
    <div>
        <h1>Secret Page</h1>
        <button onClick={() => handleLogout(props)}>Logout</button>
    </div>
)

export default Secret
Enter fullscreen mode Exit fullscreen mode

Ok, save all and run the project.

npm run start # or "yarn start"
Enter fullscreen mode Exit fullscreen mode

Because we didn't make a navigation links, so we've to manually navigate between the pages by editing the url in address bar to test the route XD

Our project structure should look like this;

├── package.json
├── public # your static content
│ ├── favicon.ico
│ ├── index.html
│ ├── logo192.png
│ ├── logo512.png
│ ├── manifest.json
│ └── robots.txt
├── README.md
└── src # our working directory
    ├── App.css
    ├── App.js
    ├── index.js
    ├── router # routing files
    │ ├── _guest.js
    │ ├── index.js
    │ └── _private.js
    ├── serviceWorker.js
    ├── setupTests.js
    ├── store # state
    │ └── auth.js
    └── views # views component
        ├── index.js
        ├── login.js
        └── secret.js
Enter fullscreen mode Exit fullscreen mode

Some of React testing files like App.test.js, App.css, are not included.

I also have created the repo for this, you can visit the link in down below for more practice:

https://github.com/bramaudi/react-boilerplate

Discussion (2)

Collapse
bramaudi profile image
Brama Udi Author

I hope it does not steal someone idea.