React - Introduction to react router v6

josec

Jose C

Posted on December 16, 2021

React - Introduction to react router v6

Installation

After creating our react app we have to install the react router v6 library to our project using npm:

npm install react-router-dom@6
Enter fullscreen mode Exit fullscreen mode

And import the BrowserRouter to our index.js:

import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";
import { BrowserRouter } from "react-router-dom";

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

Now our react application can now response to different urls.

Creating links

To create links on react we could try sing the regular html links and that would lets us navigate to different urls defined in our application but if you try you will realize that each time you click on a link you are reloading the whole page and that's something we donโ€™t do on react as react works as single page application.

Instead we use Link from the react-router-dom package we installed earlier.

import { Link } from "react-router-dom";
import "./App.css";

const App = () => {
 return <Link to="/hotels">Hotels</Link>;
};
Enter fullscreen mode Exit fullscreen mode

Image description

It's discrete but there's a link at the top left of our page but when clicking the page it doesn't get reloaded :) but itโ€™s not showing nothing new :( thou it changes the current url :)

Defining routes

On my react example app Iโ€™ve created 2 pages:

  • Hotels list.

  • Form to add new hotels to the list.

So I need two routes. One for each page.

Image description

Image description
(follow me for more design tips ๐Ÿ˜)

To create a those new routes that matches I will make use of Routes and Route from the react-router package.

import { useState } from "react";
import { Route, Routes } from "react-router";
import classes from "./app.module.css";
import Nav from "./components/Nav";
import Form from "./pages/Form";
import Hotels from "./pages/Hotels";

const App = () => {
  const hotelsArray = [
    {
      name: "Hotel Barcelona",
      rooms: 5,
      rating: 8,
    },
    {
      name: "Hotel Paris",
      rooms: 41,
      rating: 9,
    },
    {
      name: "Hotel Munich",
      rooms: 14,
      rating: 10,
    },
  ];

  const [hotels, setHotels] = useState(hotelsArray);

  const newHotelHandler = (hotel) => {
    setHotels((prevState) => [...prevState, hotel]);
  };

  return (
    <>
      <Nav />
      <div className={classes.container}>
        <Routes>
          <Route path="/hotels" element={<Hotels hotels={hotels} />} />
        </Routes>
        <Routes>
          <Route path="/new" element={<Form onSubmit={newHotelHandler} />} />
        </Routes>
      </div>
    </>
  );
};

export default App;
Enter fullscreen mode Exit fullscreen mode

On the app I have the hardcoded list of initial hotels to set is as default state and the functions that adds new hotels and updates the state.

To navigate through the pages Iโ€™ve created a new component called nav that contains all the Links:

import classes from "./Nav.module.css";
import { Link } from "react-router-dom";

const Nav = () => {
  return (
    <div className={classes.nav}>
      <div className={classes.nav__container}>
        <Link style={{textDecoration: 'none', color: 'white'}} to="/hotels">Hotels</Link>
        <Link style={{textDecoration: 'none', color: 'white'}} to="/new">Add new</Link>
      </div>
    </div>
  );
};

export default Nav;
Enter fullscreen mode Exit fullscreen mode

This way now I have a set of urls to navigate on my hotels app:

Image description

Params on url

Now I have a list of nice hotels and a form to add new ones but what if I want to check one hotel details. To do that It would be nice to have a parameterized url where to pass the hotel id so my react app can retrieve the hotel details.

To do it we have to define a new route in our app.js . I want that url to be /hotels/hotelID so my new route will be defined like this:

<Routes>
    <Route path="/hotels/:hotelId" element={<Details hotels={hotels} />} />        
</Routes>
Enter fullscreen mode Exit fullscreen mode

And on a new page I will:

  1. Read the hotel id from url.
  2. Get the hotel details (actually what I will do is get the hotel position on the hotels list).

Reading the hotel id from the url

To do that we need to import the useParams hook from the react-router-dompackage and read the params:

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

const Details = () => {
  const params = useParams();

  return <h1>{params.hotelId}</h1>;
};

export default Details;
Enter fullscreen mode Exit fullscreen mode

Image description

The params are the ones we've defined in the route path.

Getting the hotel details

import classes from "./Details.module.css";
import { Link, useParams } from "react-router-dom";

const Details = ({ hotels }) => {
  const params = useParams();
  const hotel = hotels[params.hotelId];

  return (
    <div className={classes.container}>
      <h1>Hotel: {hotel.name}</h1>
      <h2>Rooms: {hotel.rooms}</h2>
      <h3>Rating: {hotel.rating}/10</h3>
      <Link to="/hotels">
        <button className={classes.container__save}>Hotels</button>
      </Link>
    </div>
  );
};

export default Details;
Enter fullscreen mode Exit fullscreen mode

To access to this url I've updated the hotels list component so each hotel now has a Link:
import { Link } from "react-router-dom";
import classes from "./Hotels.module.css";

const Hotels = ({ hotels }) => {
  return (
    <div className={classes.container}>
      {hotels.map((hotel, key) => {
        return (
          <Link to={`/hotels/${key}`} style={{textDecoration: 'none', color: 'black'}}>
            <div key={key} className={classes.element}>
              <h1>{hotel.name}</h1>
              <h2>Rooms: {hotel.rooms}</h2>
              <h3>Rating: {hotel.rating}/10</h3>
            </div>
          </Link>
        );
      })}
    </div>
  );
};

export default Hotels;
Enter fullscreen mode Exit fullscreen mode


javascript

Navigate programmatically

Sometimes we may need to navigate our users programmatically. If you try the form to add new hotels to the list you will realize that after creating a new hotel you have to manually navigate to the hotels list with the top nav. It works but we can do it better.

At the Form.js component we have to import the useNavigate hook from the react-router-dom package.

import { useState } from "react";
import { useNavigate } from "react-router-dom";
import classes from "./Form.module.css";

const Form = ({ onSubmit }) => {
  const navigate = useNavigate();

  const [name, setName] = useState("");
  const [rooms, setRooms] = useState(0);
  const [rating, setRating] = useState(0);

  const nameHandler = (event) => {
    setName(event.target.value);
  };

  const roomsHandler = (event) => {
    setRooms(event.target.value);
  };

  const ratingHandler = (event) => {
    setRating(event.target.value);
  };

  const onSubmitHandler = () => {
      onSubmit({name: name, rooms: rooms, rating: rating});
      // After saving the hotel redirect the user to the hotels list
      navigate('/hotels')
  }

  return (
    <div className={classes.container}>
      <div className={classes.container__field}>
        <label>Hotel Name</label>
        <input onChange={nameHandler} type="text" />
      </div>
      <div className={classes.container__field}>
        <label>Rooms</label>
        <input onChange={roomsHandler} type="number" min="1" max="99" />
      </div>
      <div className={classes.container__field}>
        <label>Rating</label>
        <input onChange={ratingHandler} type="number" min="1" max="10" />
      </div>
      <button onClick={onSubmitHandler} className={classes.container__save}>Save</button>
    </div>
  );
};

export default Form;
Enter fullscreen mode Exit fullscreen mode

Nested routes

Our hotels app now works better but theres another thing we could improve. There's two routes where one is child of another: /hotels and /hotels/:hotelId.

On this example they are just two routes but on larger apps this can be annoying so let's nested instead using relative paths:

import { useState } from "react";
import { Route, Routes } from "react-router";
import classes from "./app.module.css";
import Nav from "./components/Nav";
import Details from "./pages/Details";
import Form from "./pages/Form";
import Hotels from "./pages/Hotels";

const App = () => {
  const hotelsArray = [
    {
      name: "Hotel Barcelona",
      rooms: 5,
      rating: 8,
    },
    {
      name: "Hotel Paris",
      rooms: 41,
      rating: 9,
    },
    {
      name: "Hotel Munich",
      rooms: 14,
      rating: 10,
    },
  ];

  const [hotels, setHotels] = useState(hotelsArray);

  const newHotelHandler = (hotel) => {
    setHotels((prevState) => [...prevState, hotel]);
  };

  return (
    <>
      <Nav />
      <div className={classes.container}>
        <Routes>
          <Route path="/hotels">
            <Route path="" element={<Hotels hotels={hotels} />} />
            <Route path=":hotelId" element={<Details hotels={hotels} />} />
          </Route>
        </Routes>
        <Routes>
          <Route path="/new" element={<Form onSubmit={newHotelHandler} />} />
        </Routes>
      </div>
    </>
  );
};

export default App;
Enter fullscreen mode Exit fullscreen mode

Here's link to github where you can download the project and try it and there's a demo

๐Ÿ’– ๐Ÿ’ช ๐Ÿ™… ๐Ÿšฉ
josec
Jose C

Posted on December 16, 2021

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related

ยฉ TheLazy.dev

About