Basic Routing & Auth in React
Tags: javascript, react
Published: 2020-06-19 23:45:59

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

``` bash
npm init react-app ProjectName
# or
npx create-react-app ProjectName
# if using yarn
yarn create react-app ProjectName
```

## 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`.

``` bash
npm i react-router-dom
# or
yarn add react-router-dom
```

## 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;

``` jsx
// 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
```

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 | &times; | Boolean | Only logged user can access |
| guest | &times; | Boolean | Only guest can access |
| noExact | &times; | 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**;

``` jsx
// 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
								}
							}}
						/>
					)
				}
			}}
		/>
	)
}
```

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

``` diff
- if (Auth.state.logged) {
+ if (!Auth.state.logged) {
```

``` diff
- const FALLBACK = '/login'
+ const FALLBACK = '/secret'
```

``` diff
- export const PrivateRoute = ({ component: Component, ...rest}) => {
+ export const GuestRoute = ({ component: Component, ...rest}) => {
```

## 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**;

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

## 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**

``` js
import React from 'react'

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

export default Index
```

**login.js**

``` 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
```

**secret.js**

``` 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
```

Ok, save all and run the project.

``` bash
npm run start # or "yarn start"
```

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;

``` bash
├── 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
```

> 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:

<a href="https://github.com/bramaudi/react-boilerplate" target="_blank" title="react-boilerplate">https://github.com/bramaudi/react-boilerplate</a>