React technical principles and code development practice: from routing to nested routing

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.