React-router
Introduction
This page is about routering with React.
Setup
Install package for Router
npm i react-router-dom
Add BrowserRouter to Index.js
...
import { BrowserRouter } from 'react-router-dom';
...
ReactDOM.render(
<React.StrictMode>
<BrowserRouter>
<App />
</BrowserRouter>
</React.StrictMode>,
document.getElementById('root')
);
Add Routes to App.js
We need to a routes to the the router we support.
import { BrowserRouter as Router, Switch, Route, Link } from 'react-router-dom';
import Explore from './components/Explore';
import Home from './components/Home';
import Messages from './components/Messages';
import Profile from './components/Profile';
function App() {
return (
<Router>
<div>
<h2>Welcome to React Router Tutorial</h2>
<nav className="navbar navbar-expand-lg navbar-light bg-light">
<ul className="navbar-nav mr-auto">
<li><Link to={'/'} className="nav-link"> Home </Link></li>
<li><Link to={'/explore'} className="nav-link">Explore</Link></li>
<li><Link to={'/messages'} className="nav-link">Messages</Link></li>
<li><Link to={'/profile'} className="nav-link">Profile</Link></li>
</ul>
</nav>
<hr />
<Switch>
<Route exact path='/' component={Home} />
<Route path='/explore' component={Explore} />
<Route path='/messages' component={Messages} />
<Route path='/profile' component={Profile} />
</Switch>
</div>
</Router>
);
}
export default App;
Create Pages
Functional Based
Create a component for each page
import React from 'react'
const Home = () => {
return (
<div>
<h2>Home</h2>
</div>
);
};
export default Home
Class Component Based
Create a component for each page
import React, { Component } from 'react';
class Profile extends Component {
render() {
return (
<div>
<h2>Profile</h2>
</div>
);
}
}
export default Profile;
Component Based
This allows us to work with the activeClassName and exact to highlight the list which is active.
<Router>
<Nav authenticated={!defaultToSignIn} />
<hr />
<Switch>
<Route path="/" component={Home} exact />
<Route path="/auth/login" component={Login} />
<Route path="/auth/Register" component={Register} />
<Route path="/auth/logout" component={Logout} />
</Switch>
</Router>
And for some of the NavLink routes
<li className="nav-item">
<NavLink
to="/home"
aria-current="page"
className="nav-link"
exact
>
Home
</NavLink>
</li>
</ul>
<ul className="navbar-nav ms-auto mb-2 mb-lg-0">
<li className="nav-item">
<NavLink to="/explore" aria-current="page" className="nav-link">
Explore
</NavLink>
</li>
<li className="nav-item">
<NavLink to="/messages" aria-current="page" className="nav-link">
Messages
</NavLink>
</li>
Prompt Component
We can prevent leaving of a page with the Prompt component. For example.
...
return (
<div className="Login">
{error && <p intent="danger">{error}</p>}
<Prompt
when={!usernameValid || !passwordValid}
message="Leaving page will loose your data"
/>
<Form onSubmit={handleSubmit} className="row g-3 needs-validation">
<div>
<div className="col-md-4">
<Form.Group size="lg" controlId="username">
<Form.Label>Username</Form.Label>
<Form.Control
...
Adding a 404 Page
To implement this we just create a route with no path and a component
<Router>
<Nav authenticated={!defaultToSignIn} />
<hr />
<Switch>
<Route
exact
path="/"
render={() =>
defaultToSignIn ? (
<Redirect to="/auth/login" />
) : (
<Redirect to="/home" />
)
}
/>
<ProtectedRoute path="/" component={Home} exact />
<ProtectedRoute path="/explore" component={Explore} />
<ProtectedRoute path="/messages" component={Messages} />
<ProtectedRoute path="/profile" component={Profile} />
<Route path="/auth/login" component={Login} />
<Route path="/auth/Register" component={Register} />
<Route path="/auth/logout" component={Logout} />
<Route component={PageNotFound} />
</Switch>
</Router>
Rendering Component with Parameters
When you need to add props to the component to render we use the render property which takes a function as an argument and should return the Component for the page.
Router
<Router>
<Switch>
<Route path="/auth/logout" component={Logout} />
<Route path="/compwithprops" render={() => {
return <CompWithProps prop1="#ff0000" prop2='Red'/>
}) />
<Route component={PageNotFound} />
</Switch>
Component
For the component we need to wrap it with withRouter in a HOC.
import React from "react";
const CompWithProps = ({prop1,prop2}) => (
<div>
..
</div>
);
CompWithProps.proptypes = {
prop1: PropTypes.string.isRequired,
prop2: PropTypes.string.isRequired,
}
export default withRouter(CompWithProps);
Custom Route with Parameters
Here we create a custom route which takes a parameter :eid
...
<CustomRoute path={'/api/orders/:eid'} component={Home} />
...
I the custom route we now Here we can create a Route which pulls this data in the props. Like the example above we can render the component Home and the eid will be available in the props.match.params.eid
import React from "react";
import { Route } from "react-router-dom";
const CustomRoute = ({ component: ComponentToRender, ...rest }) => (
<Route
{...rest}
render={(props) => {
alert(`Cusom Route ${props.match.params.eid} `);
return <ComponentToRender {...props} />;
}}
/>
);
export default CustomRoute;
Protected Route
Authentication Service
We create an authService which emulates the following
import Cookies from "universal-cookies";
const cookies = new Cookies();
const authService = {
isAuthenticated() {
return cookies.get("auth") === "true";
},
signIn(cb) {
cookies.set("auth", true, { path: "/" });
setTimeout(cb, 100);
},
signOut(cb) {
cookies.set("auth", false, { path: "/" });
setTimeout(cb, 100);
},
};
export default authService;
Create Private Route
We create this function which checks if the user is authenticated and either renders the Component passed in or redirects to the login page (commented out). This is great for a new user but if we click a bookmark our expectation is that we would be redirected to login, enter credentials and then redirect back to the original request. This is achieved with the highlighted code.
import React from "react";
import { Redirect } from "react-router-dom";
import authService from "../service/authService";
const PrivateRoute = ({ component: Component, ...rest }) => (
<Route {...rest}
render={(props) => {
authService.isAuthenticated() ? (
<Component {...props} />
) : (
// <Redirect to="/login" />
<Redirect to={
pathname: '/login',
state: { target: props.location }
} />
);
}}
/>
);
export default PrivateRoute;
Change Routes to Use Private Route
Now we put the private where appropriate
<Router>
<PrivateRoute path="/" component={Home} exact />
<PrivateRoute path="/explore" component={Explore} />
<Privateoute path="/messages" component={Messages} />
<PrivateRoute path="/profile" component={Profile} />
<Route path="/auth/login" component={Login} />
<Route path="/auth/Register" component={Register} />
<Route path="/auth/logout" component={Logout} />
<Route component={PageNotFound} />
</Switch>
</Router>
Logout
Here is the example of the logout function.
import { Redirect } from "react-router-dom";
import authService from "../service/authService";
class Logout extends React.Component {
constructor(props) {
super(props);
}
componentWillMount = () => {
authService.signOut(() => {});
};
render = () => {
return <Redirect to={"/login"} />;
};
}
export default Logout;
Router Children Component
All router children components get the following props.
- match Exact Match, Params, Path and matching Url
- location Key, path name and search
- history This is the browser history
Transitions
You need to provide a class prefix in this case trans.
import {TransitionGroup, CSSTransition} from 'react-transition-group'
<TransactionGroup>
<CSSTransition key={location.key} classNames={'trans'} timeout={1000}>
<Switch location={location}>
<Route path={`${match.url}`} component={LoremNumber} exact/>
<Route path={`${match.url}/:id`} component={LoremNumber} exact/>
</Switch>
</TransactionGroup>
And the CSS
.trans-enter {
opacity: 0;
z-index: 1;
}
.trans-exit {
display: none
}
.trans-enter.trans-enter-active {
opacity: 1;
transition: opacity 1000ms ease-in;
}