React router: build multi page SPA

Posted by avalonindigo on Wed, 02 Feb 2022 21:17:25 +0100

Using Router, you can build a multi page single page app

1. Install the react router:

npm intall react-router-dom

2 basic usage

2.1 App.js import Route component

import { Route } from "react-router-dom";

2.2 App.js definition path:

There are two paths:

http://localhost:3000/welcome
http://localhost:3000/products

Load corresponding components in different paths.

// import Route from the library
import { Route } from "react-router-dom";
import Welcome from "./components/Welcome";
import Products from "./components/Products";

function App() {
  return (
    <div>
      <Route path="/welcome">
        <Welcome />
      </Route>
      <Route path="/products">
        <Products />
      </Route>
    </div>
  );
}

export default App;

2.3 index.js code:

import ReactDOM from "react-dom";
import { BrowserRouter } from "react-router-dom";

import "./index.css";
import App from "./App";

ReactDOM.render(
  <BrowserRouter>
    <App />
  </BrowserRouter>,
  document.getElementById("root")
);

Because welcome JS and products JS are route related components. It is more appropriate to put them in a folder named src/pages than src/components.

3 add page links

3.1 standard link mode, use < a > to realize page navigation.

This method has a disadvantage: every time you click the link, the page will be reloaded and the App will restart, so the current state will be lost.

// MainHeader.js
const MainHeader = () => {
  return (
    <header>
      <nav>
        <ul>
          <li>
            <a href="/welcome">Welcome</a>
          </li>
          <li>
            <a href="/products">Products</a>
          </li>
        </ul>
      </nav>
    </header>
  );
};
export default MainHeader;
// App.js 
function App() {
  return (
    <div>
      <MainHeader />
      <main>
        <Route path="/welcome">
          <Welcome />
        </Route>
        <Route path="/products">
          <Products />
        </Route>
      </main>
    </div>
  );
}

3.2 Link component using react router DOM

Change < a > to < link >, and then href to. The following code will prevent the default behavior of the browser and the page will not be reloaded:

import { Link } from "react-router-dom";
const MainHeader = () => {
  return (
    <header>
      <nav>
        <ul>
          <li>
            <Link to="/welcome">Welcome</Link>
          </li>
          <li>
            <Link to="/products">Products</Link>
          </li>
        </ul>
      </nav>
    </header>
  );
};

export default MainHeader;

3.3 NavLink and activeClassName

Although using the Link component can avoid page reloading, users cannot directly see which Link they have clicked.
By changing Link to NavLink, you can use activeClassName to add CSS style to the current Link and set the style of the Link,

import { NavLink } from "react-router-dom";
import classes from "./MainHeader.module.css";
const MainHeader = () => {
  return (
    <header className={classes.header}>
      <nav>
        <ul>
          <li>
            <NavLink activeClassName={classes.active} to="/welcome">
              Welcome
            </NavLink>
          </li>
          <li>
            <NavLink activeClassName={classes.active} to="/products">
              Products
            </NavLink>
          </li>
        </ul>
      </nav>
    </header>
  );
};
export default MainHeader;
// MainHeader.module.css
.header {
  width: 100%;
  height: 5rem;
  background-color: #044599;
  padding: 0 10%;
}

.header nav {
  height: 100%;
}

.header ul {
  height: 100%;
  list-style: none;
  display: flex;
  padding: 0;
  margin: 0;
  align-items: center;
  justify-content: center;
}

.header li {
  margin: 0 1rem;
  width: 5rem;
}

.header a {
  color: white;
  text-decoration: none;
}

.header a:hover,
.header a:active,
.header a.active {
  color: #95bcf0;
  padding-bottom: 0.25rem;
  border-bottom: 4px solid #95bcf0;
}

4 Dynamic Routing

4.1 register dynamic routing with parameters

For example, to add a series of products' detail pages:

The colon must be added,

   <Route path="/product-detail/:productId">
      <ProductDetail />
    </Route>

,: productId is a placeholder and a dynamic segment
This syntax tells the React Router that the path is:

ourdomain.com/product-detail/ <any thing...>

Load < productdetail / > page

4.2 extracting dynamic parameters using useParams

The hook function useParams returns an object composed of a key value pair. Key is the parameter name of the dynamic path.

Registration path: < route path = "/ product detail /: productid" >
Input path: localhost: 3000 / product detail / product1
params. The value of productid is product1

import { useParams } from "react-router-dom";

const ProductDetail = () => {
  const params = useParams();
  return (
    <section>
      <h1>Product Detail</h1>
      <p>{params.productId}</p>
    </section>
  );
};
export default ProductDetail;

5. Configure routing using switch and exact

For the current problem, all routes matching the input path are loaded by default:

function App() {
  return (
    <div>
      <MainHeader />
      <main>
        <Route path="/welcome">
          <Welcome />
        </Route>
        <Route path="/products">
          <Products />
        </Route>
        <Route path="/products/:productId">
          <ProductDetail />
        </Route>
      </main>
    </div>
  );
}

For example:

http://localhost:3000/products/p2

This path matches two routes: / products and / products/:productId, so two pages are loaded at the same time.
/products/:productId starts with / products, so two paths are matched, regardless of the order before and after route registration. Loading multiple pages at the same time, sometimes this is the expected behavior, sometimes it is not, there is no right or wrong.

5.1 using Switch components to achieve unique matching

The Switch component ensures that only one route is loaded for one path, and it is the first matching route:
According to the following Route registration order, / products/:productId will not be loaded because it is the second matching Route, and only the first matching Route: / products will be loaded.
(at least this is the behavior of React Router 5)

import { Route, Switch } from "react-router-dom";
import Welcome from "./pages/Welcome";
import Products from "./pages/Products";
import MainHeader from "./components/MainHeader";
import ProductDetail from "./pages/ProductDetail";

function App() {
  return (
    <div>
      <MainHeader />
      <main>
        <Switch>
          <Route path="/welcome">
            <Welcome />
          </Route>
          <Route path="/products">
            <Products />
          </Route>
          <Route path="/products/:productId">
            <ProductDetail />
          </Route>
        </Switch>
      </main>
    </div>
  );
}
export default App;

5.2 exact attribute

To solve the above problem that / products/:productId will not be loaded, there are two solution s:

5.2.1 adjust routing sequence

For example, call the top and bottom order of two matching routes:
So / products/p1 will match / products/:productId instead of / products

<Route path="/products/:productId">
	<ProductDetail />
</Route>
<Route path="/products">
	<Products />
</Route>

5.2.2 exact matching using exact attribute

Keep the original route order unchanged, add the exact attribute to the specific route, and tell the react router to match the route if and only if the paths match completely.

The following code, / products/xyz no longer matches / products/:productId

function App() {
  return (
    <div>
      <MainHeader />
      <main>
        <Switch>
          <Route path="/welcome">
            <Welcome />
          </Route>
          <Route path="/products" exact>
            <Products />
          </Route>
          <Route path="/products/:productId">
            <ProductDetail />
          </Route>
        </Switch>
      </main>
    </div>
  );
}

6 nested routing

App.js is not the only component that can set routes. Routing can also be set, and all components can add routes: for example:

// Welcome.js
import { Route } from "react-router-dom";
const Welcome = () => {
  return (
    <section>
      <h1>The welcome page</h1>
      <Route path="/welcome/new-user">
        <p>Welcome, new user!</p>
      </Route>
    </section>
  );
};
export default Welcome;

7. Redirect using redirect component

In the following example, if the user enters / automatically redirects to the route / welcome, the exact attribute of < route path = "/" exact > is very important, because / matches any route, and there will be an infinite loop without this attribute.

import { Route, Switch, Redirect } from "react-router-dom";
import Welcome from "./pages/Welcome";
import Products from "./pages/Products";
import MainHeader from "./components/MainHeader";
import ProductDetail from "./pages/ProductDetail";

function App() {
  return (
    <div>
      <MainHeader />
      <main>
        <Switch>
          <Route path="/" exact>
            <Redirect to="/welcome" />
          </Route>
          <Route path="/welcome">
            <Welcome />
          </Route>
          <Route path="/products" exact>
            <Products />
          </Route>
          <Route path="/products/:productId">
            <ProductDetail />
          </Route>
        </Switch>
      </main>
    </div>
  );
}
export default App;

Topics: Javascript Front-end React