Author: Zen and the Art of Computer Programming
1. Background Introduction
For web developers, it is especially important to master React’s routing mechanism. As a well-known front-end framework for React, there are many ways to implement routing. This article will elaborate on the following two aspects:
1. What is front-end routing? 2. Front-end routing principles and related configurations.
By elaborating on the above two aspects, I hope readers can better understand and master React’s routing mechanism.
2. Core concepts and connections
Before starting the text, let us review the basic concepts and development process of React.
2.1 React basic knowledge
React is a JavaScript library specifically designed for building user interfaces (UI). It was born at Facebook in 2013 and was built by a group of researchers and engineers to solve the problem of status updates in a declarative way. It has powerful componentization features that can easily split complex applications, improving development efficiency. In addition, React also supports server-side rendering, further improving the user experience.
Core concepts of React:
1. Component: an independent and reusable UI module with self-managed status and life cycle. The creator of a component defines the component’s properties and behavior, and receives data passed by the parent component through the props object. Components have their own lifecycle functions that can be called at different stages.
2. JSX (JavaScript Extension): JSX is a syntax extension of JavaScript, which allows JSX to be compiled into ordinary JavaScript objects. JSX can be used both to describe UI elements and to define components.
3. Virtual DOM: React’s DOM operation essentially modifies the real DOM. Therefore, in order to improve performance, React will package multiple changes into a batch, render the virtual DOM only once, and then convert this virtual DOM into an actual DOM. Doing this helps reduce unnecessary re-rendering and improves performance.
4. One-way data flow: React implements one-way data flow through parent-child component communication, that is, it can only be passed from parent component to child component, not the other way around. Therefore, it is necessary to ensure the accuracy and completeness of data to avoid state synchronization problems caused by “one-way data flow”.
5. Life cycle: React provides some life cycle methods for components, allowing corresponding functions to be executed during a specific period. For example, the componentDidMount() method will be executed after the component is loaded for the first time, and the componentDidUpdate() method will be executed after the component completes the update, etc.
2.2 Create React project
First, create a new directory as the root directory of your React project. Enter the root directory and enter the command:
npx create-react-app my-project
Here, the npx command is a new command in npm 5.2 + version, which is used to run the npm package installer. This command will automatically install the latest version of create-react-app. If it is not installed locally, it will be automatically downloaded. After that, follow the prompts and wait for the project to be initialized.
If you want to create a React project based on TypeScript, you can use the following command:
npx create-react-app my-typescript-app --template typescript
Note that TypeScript templates are currently still in the preview stage and may have some issues.
2.3 Development Environment Preparation
First, we need to install Node.js. You can download the installer from the official website to install it, or install it directly through the software management tool of each operating system.
Then, open a terminal or command line tool, switch to the React project root directory, and enter the following command to start the project:
npm start
After successfully starting the project, the browser will open the http://localhost:3000/
address by default and display the welcome page.
Next, we need to install several necessary dependency packages, namely:
npm install react-router-dom --save npm install [email protected] --save # This is the latest version of history routing npm install @types/node @types/react @types/react-dom @types/jest --save-dev
Among them, react-router-dom is the official routing manager of React, and history is a library that implements the history recording function; other dependency packages are type hint files to facilitate code editing.
Finally, create an entry file index.tsx to write React components and routing.
3. Detailed explanation of core algorithm principles, specific operation steps and mathematical model formulas
There are four main modes of routing in React:
- HashRouter: Routing based on the hash value in the URL, suitable for single-page applications;
- BrowserRouter: Routing based on HTML5’s History API and window.location object, suitable for multi-page applications;
- MemoryRouter: Routing based on the history object in memory, suitable for test scenarios;
- Switch: Only render the first Route or element that matches the current URL;
A simple demonstration of using HashRouter for front-end routing
(1) Configure project routing information
First, we need to configure the project routing information, create a new routes.ts
file in the src folder, and write the following code:
import React from'react'; import {Route} from "react-router-dom"; import Home from './pages/Home'; import About from './pages/About'; import Login from './pages/Login'; const Routes = () => ( <div> <Route exact path="/" component={Home}/> <Route path="/about" component={About}/> <Route path="/login" component={Login}/> </div> ); export default Routes;
Here, we created a Routes
component, which defines three routes. Each route corresponds to a component and the corresponding URL path is different.
(2) Create component
Then, we create three component files, namely Home.tsx, About.tsx, and Login.tsx. The code for the Home component is as follows:
import React from'react'; function Home() { return ( <h1>Welcome to home page!</h1> ); } export default Home;
The code for the About component is as follows:
import React from'react'; function About() { return ( <p>This is a demo app for learning React routing.</p> ); } export default About;
The code for the Login component is as follows:
import React from'react'; function Login() { return ( <form> <label htmlFor="username">Username:</label> <input type="text" id="username" name="username"/> <br /> <label htmlFor="password">Password:</label> <input type="password" id="password" name="password"/> <br /> <button type="submit">Log in</button> </form> ); } export default Login;
(3) Connect routing and components
Finally, we connect the route and the component, import the component in the index.tsx file, connect the route, and export the component. The code is as follows:
import React from'react'; import ReactDOM from'react-dom'; import {HashRouter as Router, Link, Route} from'react-router-dom' import App from './App'; // Routing component to be connected import * as serviceWorkerRegistration from './serviceWorkerRegistration'; ReactDOM.render( <React.StrictMode> <Router> <nav> <ul> <li><Link to="/">Home</Link></li> <li><Link to="/about">About</Link></li> <li><Link to="/login">Login</Link></li> </ul> </nav> <main> <Route exact path="/" component={App} /> <Route path="/about" component={App} /> <Route path="/login" component={App} /> </main> </Router> </React.StrictMode>, document.getElementById('root') ); // If you want your app to work offline and load faster, you can change // unregister() to register() below. Note this comes with some pitfalls. // Learn more about service workers: https://cra.link/PWA serviceWorkerRegistration.register();
(4) Effect display
Enter http://localhost:3000
in the browser, and three routing links will be displayed on the page. Click any link and the page will switch to the corresponding component page.
4. Specific code examples and detailed explanations
Below, we will gradually expand on the detailed introduction to React routing based on code examples.
4.1 Configure project routing information
We first configure the project routing information and create a new file /src/routes.ts
:
import React from'react'; import {Route} from "react-router-dom"; import Home from './pages/Home'; import About from './pages/About'; import Login from './pages/Login'; const Routes = () => ( <div> <Route exact path="/" component={Home}/> <Route path="/about" component={About}/> <Route path="/login" component={Login}/> </div> ); export default Routes;
We have defined three routes: homepage (/
), about (/about
), and login (/login
).
4.2 Create Component
We create three more component files, namely /src/pages/Home.tsx
, /src/pages/About.tsx
, /src/pages/ Login.tsx
. Their contents are as follows:
import React from'react'; function Home() { return ( <h1>Welcome to home page!</h1> ); } export default Home;
import React from'react'; function About() { return ( <p>This is a demo app for learning React routing.</p> ); } export default About;
import React from'react'; function Login() { return ( <form> <label htmlFor="username">Username:</label> <input type="text" id="username" name="username"/> <br /> <label htmlFor="password">Password:</label> <input type="password" id="password" name="password"/> <br /> <button type="submit">Log in</button> </form> ); } export default Login;
4.3 Connect routing and components
Then, we connect the routes and components, export the Routes
component, and render it into the HTML document.
In the /src/index.tsx
file, import the Routes
component and render it into the HTML document:
import React from'react'; import ReactDOM from'react-dom'; import {BrowserRouter as Router, Link, Route} from'react-router-dom' import App from './App'; import * as serviceWorkerRegistration from './serviceWorkerRegistration'; import Routes from "./routes"; ReactDOM.render( <React.StrictMode> <Router> <nav> <ul> <li><Link to="/">Home</Link></li> <li><Link to="/about">About</Link></li> <li><Link to="/login">Login</Link></li> </ul> </nav> <main> <Routes /> </main> </Router> </React.StrictMode>, document.getElementById('root') ); // If you want your app to work offline and load faster, you can change // unregister() to register() below. Note this comes with some pitfalls. // Learn more about service workers: https://cra.link/PWA serviceWorkerRegistration.unregister();
4.4 Set basic style
For the sake of beauty, we set basic styles, such as font size, color, margins, spacing, etc., edit the /public/index.html
file and add the following CSS styles:
body{ font-family: sans-serif; margin: 0px; padding: 0px; } nav ul{ list-style: none; display: flex; justify-content: center; background-color: #333; color: white; padding: 10px; } navli{ margin: 0px 10px; cursor: pointer; } main{ max-width: 960px; margin: 0 auto; padding: 20px; }
4.5 Add Icon
In order to add visual effects, we add some icons, such as React Logo, edit the /public/index.html
file and add the following code:
<header> <h1>React Routing Demo</h1> </header>
4.6 Install dependent packages
In order to use React Router, we need to install the dependency package react-router-dom
.
We first install React Router and type hint files in the project root directory:
npm i react-router-dom @types/react-router-dom -S
Then, install axios
, a library for sending HTTP requests:
npm i axios -S
Axios will be used to obtain user login information.
4.7 Implementing the login function
First, we introduce the Axios library in the Login.tsx
file:
import React, { useState } from'react'; import axios from 'axios'; async function loginUser(event){ event.preventDefault(); const data = new FormData(event.target); try { await axios({ method: 'POST', url: '/api/users/login', data: {...data}, }); alert("Logged In"); console.log("Success!"); location.reload(); } catch (error) { console.log(error); alert("Failed To Log In"); } }
Here, we define an asynchronous function loginUser
, which accepts an event parameter event
, calls preventDefault to prevent the form from default submission, reads FormData to construct the form data, and uses Axios to send POST Request to the /api/users/login
interface to send the form data to the backend.
If the sending is successful, we pop up a success message and refresh the page; otherwise, we pop up an error message.
We also need to configure the backend API service, edit the /server.js
file and add the following code:
const express = require('express'); const bodyParser = require('body-parser'); const cors = require('cors'); const jwt = require('jsonwebtoken'); const bcrypt = require('bcryptjs'); const port = process.env.PORT || 3001; const app = express(); app.use(bodyParser.urlencoded({ extended: true })); app.use(bodyParser.json()); app.use(cors()); let users = []; app.post('/api/users/signup', async (req, res) => { const hashedPassword = await bcrypt.hashSync(req.body.password, 10); const user = {...req.body, password: hashedPassword }; users.push(user); res.status(201).send(`User created!`); }); app.post('/api/users/login', async (req, res) => { const foundUser = users.find((u) => u.email === req.body.email & amp; & amp; bcrypt.compareSync(req.body.password, u.password)); if (!foundUser) { res.status(401).send(`Invalid email or password.`); } else { const token = jwt.sign({ userId: foundUser._id },'secretkey', { expiresIn: '24h'}); res.cookie('jwt', token, { httpOnly: true }); res.redirect('/'); } }); app.listen(port, () => { console.log(`Server started on port ${port}`); });
Here, we define two API interfaces:
/api/users/signup
: User registration interface. Receive the request body data, use bcrypt to encrypt the password, store it in the users array, and return JSON data./api/users/login
: User login interface. Receive the request body data, find if there is a user with the same email address and password, generate a JWT Token, set the cookie attributes and redirect to the home page.
4.8 Achieve user authentication
In order to prevent unauthorized users from accessing the page, we need to implement user authentication.
Edit the Routes.tsx
file and add authentication logic:
import React from'react'; import { Redirect, Route } from'react-router-dom'; import Auth from './Auth'; import Home from '../pages/Home'; import About from '../pages/About'; import Login from '../pages/Login'; const PrivateRoute = ({component: Component,...rest}) => ( <Route {...rest} render={(props) => localStorage.getItem('jwt')? (<Component {...props} />) : (<Redirect to={<!-- -->{ pathname: '/login', state: {from: props.location}}}/>)} /> ); const Routes = () => ( <> <PrivateRoute exact path='/' component={Home} /> <PrivateRoute path='/about' component={About} /> <Route exact path='/login' component={Login} /> </> ); export default Routes;
Here, we import the Auth
component and wrap the Route
component to implement user authentication.
When a user accesses an unauthorized page, the PrivateRoute
component will check whether there is a JWT Token in LocalStorage. If so, the target page will be rendered, otherwise it will be redirected to the login page.
Edit the App.jsx
file and introduce the Auth
component:
import React from'react'; import { Route, Switch, useLocation } from'react-router-dom'; import Header from './Header'; import NotFoundPage from './NotFoundPage'; import Footer from './Footer'; import Auth from './Auth'; import LoginForm from './LoginForm'; const authRoutes = [ '/', '/about', ]; const App = () => { const location = useLocation(); return ( <div className='container'> <Header /> {!authRoutes.includes(location.pathname) & amp; & amp; ( <Switch> {/* other routes */} <Route exact path={'/login'} render={() => !localStorage.getItem('jwt')? ( <LoginForm /> ) : ( <Redirect to={<!-- -->{ pathname: '/', state: { prevPathname: location.state?.prevPathname '/', }, }} /> ) } ></Route> {/* not found route */} <Route component={NotFoundPage}></Route> </Switch> )} {authRoutes.includes(location.pathname) & amp; & amp; ( <Switch> {/* authenticated routes */} <Route exact path={`/login`} component={LoginForm}></Route> {/* not found route */} <Route component={NotFoundPage}></Route> </Switch> )} <Footer /> </div> ); }; export default App;
Here, we determine whether the current page is a page that does not require authentication. If so, render a normal route; if not, check whether LocalStorage has a JWT Token. If so, redirect to the login page; if not, render the login page.