React Router Nested Routes
A common paradigm when creating web applications is to have a "list page" and a "detail page". Consider a product search on Amazon. The results that show up from the search is a list of products with some information about each (price, ratings, title, part of the description, etc.). When you click on the product, you're taken to a "detail page" which contains only information (in much more detail than before) about that one product.
See the examples below:
List Page
Detail Page
We are going to create an example website where we display a list of products and then individual detail pages for each product.
By this point you should have already read the write-up on React Router, if you have not, stop doing this and do that first. Anyways, we are going to use a basic Single Page Application (SPA) with 3 links as our starting point.
Here it is below:
The code looks like this:
App.js
import React from 'react';
import Navbar from './Navbar';
import Home from './Home';
import About from './About';
import ProductList from './ProductList';
import './App.css'
import {Switch, Route} from 'react-router-dom';
const App = () => {
return (
<div>
<Navbar />
<Switch>
<Route exact path="/" component={Home} />
<Route path="/about" component={About} />
<Route path="/products" component={ProductList} />
</Switch>
</div>
);
};
export default App;
Home.js
import React from 'react';
const Home = () => {
return (
<div className='home'>
<h1>Home Page</h1>
<p>We are Plumbers and stuff</p>
</div>
);
};
export default Home;
About.js
import React from 'react';
const About = () => {
return (
<div className='about'>
<h1 style={{marginTop: 0}}>About us</h1>
<p>Lorem, ipsum dolor sit amet consectetur adipisicing elit. Illo amet in itaque atque hic! Aliquam, saepe nulla officia culpa vitae velit a qui iusto dolore accusamus aspernatur, nostrum maxime ullam. </p>
</div>
);
};
export default About;
ProductList.js
import React from 'react';
const ProductList = () => {
return (
<div className='product-list'>
<h1>Products</h1>
</div>
);
};
export default ProductList;
Nested Routes
We have a page about the Products this business offers. Perhaps when the user navigates to the /products
page, we want to show a list of products to them. However, we also want to allow them to click on one of the products and learn a lot more about that one particular product. Assuming we plan on offering many products (and perhaps adding to the list as we grow as a business), we definitely aren't interested in creating a new React component for every single product we offer. Instead, we want to create a single version of a "Product Detail Page" and just substitute in information about the particular product the user clicks on.
Typically we will be dealing with JSON data from an API. For this practice, we will just be getting it from a JSON file that we have created ourselves. Per normal, information stored in a database will have a unique _id
property, so we have given each item in our JSON file it's own _id
.
products.json
[
{
"name": "Septic Cleaner",
"price": 950,
"_id": "123",
"description": "You know what this is, don't pretend like you don't"
},
{
"name": "Snaking Device",
"price": 500,
"_id": "134",
"description": "You know what this is you little snake, don't pretend like you don't"
},
{
"name": "Tree Digging Tool",
"price": 5000,
"_id": "352",
"description": "For all the diggers who love trees"
},
{
"name": "Drain Replacement",
"price": 150,
"_id": "613",
"description": "For you hairy people, you know who you are"
}
]
In order to display these we will need to import the JSON file into our ProductList.js
and then map through the array of products.
ProductList.js
import React from 'react';
import {Link} from 'react-router-dom';
import list from './products.json';
const ProductList = () => {
**const productsMapped = list.map(product => <Link key={product._id} to={`/products/${product._id}`}>{product.name}</Link> )**
return (
<div className='product-list'>
<h1>Products</h1>
**<div className='product-links'>
{productsMapped}
</div>**
);
};
export default ProductList;
So lets break down what we did above:
Route Params
React Router has a way to let us specify an unknown portion of a Route's path
using something called a "route param". In the above example, we don't know what the path will be after /products/
(/products/???
).
Using route parameters, we can say "whatever ends up in this part of the path, let's give it a variable name so we can access it later and figure out how to render the correct thing."
const productsMapped = list.map(product => <Link key={product._id} to={'/products/${product._id}'}>{product.name} )
We are mapping through our JSON list of products and returning <Link />
components. Within the Link
components, we are using our _id
as the key, and we are putting it at the end of our /products
endpoint. The product.name
is being used to display the link using our Product Name.
The reason we add the product._id
onto our endpoint (to={/products/${product._id}
) is because, with Nested Routes we need to dynamically generate a unique endpoint for each "Product Detail Page" so that our website knows which one to take us to.
Let's take a look at our website now:
Our product names are now displaying! They are also serving as links, which means that if you click on them it will change the url of our page. Notice that each time I click one of the links it adds the _id
for that specific product in the URL after products/
.
Now we will add in our Route
so that we can display the rest of our Product Details once the link gets clicked.
ProductList.js
import React from 'react';
import Product from './Product';
import {Link, Switch, Route} from 'react-router-dom';
import list from './products.json';
const ProductList = () => {
const productsMapped = list.map(product => <Link key={i} to={`/products/${product._id}`}>{product.name}</Link> )
return (
<div className='product-list'>
<h1>Products</h1>
<div className='product-links'>
{productsMapped}
</div>
**
<Switch>
<Route path="/products/:_id" component={Product} />
</Switch>
**
</div>
);
};
export default ProductList;
Product.js
import React from 'react';
import products from './products.json'
const Product = (props) => {
let {_id} = props.match.params
const product = products.find(product => product._id === _id)
let {name, price, description} = product
return (
<div className='product'>
<h1>{name}</h1>
<h3>Price: ${price}</h3>
<h3>{description}</h3>
</div>
);
};
export default Product;
First, I will show you our website, then I will explain what is going on with our Route
and our Product.js
Component.
So if you look at our ProductList.js
you will see that we added the following line:
<Switch>
<Route path="/products/:_id" component={Product} />
</Switch>
This is just like the Switch
and Route
that we have used previously, the only difference is that in our path=
we are adding a Route Param, that is what the :_id
is after our /products/
.
Let's head over to our Product.js
Component now and break it down.
First, we will console.log props
:
Notice that our props has several new properties such as History, Match, Location, etc... If we go into our props.match
we will see another property called params
and inside there we see an object {_id: 123}
. The key value _id
comes from in our Route
, specifically from the path="/products/**:_id**"
. The value 123
comes from our Link
specifically from to={'/products/**${product._id}**'}
. So depending on which Link gets clicked, the value of our props.match.params._id
will be different.
Next, we are looping through our products.json
file and grabbing just the product info that matches our _id
that is found in props.match.params._id
.
Finally, we are simply destructuring that content and displaying it in our Product.js
component.
One last thing before you go!
This website is nice, but it is not exactly like the example at the beginning of www.amazon.com. All we have to do to make it so that our links take us to new pages, is move our Route
from our ProductList.js
over to our App.js
.
App.js
import React from 'react';
import Navbar from './Navbar';
import Home from './Home';
import About from './About';
import ProductList from './ProductList';
import Product from './Product';
import './App.css'
import {Switch, Route} from 'react-router-dom';
const App = () => {
return (
<div>
<Navbar />
<Switch>
<Route exact path="/" component={Home} />
<Route path="/about" component={About} />
<Route exact path="/products" component={ProductList} />
<Route path="/products/:_id" component={Product} />
</Switch>
</div>
);
};
export default App;
This will make our website look more like the Amazon example:
Now you know how to do Nested Routes and Route Params!!