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

Screen-Shot-2019-02-27-at-2.18.01-PM

Detail Page

Screen-Shot-2019-02-27-at-2.22.07-PM

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:

ezgif.com-video-to-gif

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:

ezgif.com-resize

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.

ezgif.com-video-to-gif--2-

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:

Screen-Shot-2019-02-28-at-11.54.02-AM

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:

ezgif.com-video-to-gif--3--1

Now you know how to do Nested Routes and Route Params!!