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
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")
);
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>;
};
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.
(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;
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;
This way now I have a set of urls to navigate on my hotels app:
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>
And on a new page I will:
- Read the hotel id from url.
- 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-dom
package and read the params:
import { useParams } from "react-router-dom";
const Details = () => {
const params = useParams();
return <h1>{params.hotelId}</h1>;
};
export default Details;
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;
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;
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;
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;
Here's link to github where you can download the project and try it and there's a demo
Top comments (2)
I think you've got a typo in the title.
True! I solved thanks 👍🏻