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
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
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
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
}
}}
/>
)
}
}}
/>
)
}
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) {
- const FALLBACK = '/login'
+ const FALLBACK = '/secret'
- 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;
// 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
import React from 'react'
const Index = () => <h1>Index Page</h1>
export default Index
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
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
Ok, save all and run the project.
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;
├── 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:
Top comments (1)
I hope it does not steal someone idea.