In the previous lesson we generated a list of houses.

In this lesson the goal is to make each of them clickable, and once clicked, we’ll transition the page to a new URL, which contains the ID of the house, and the site should show us a detail view.

The list will disappear and instead we’ll get more details about the house we selected.

First, I’m going to assign an id number to each house in the houses.json file:

pages/houses.json

[
  {
    "id": "1",
    "picture": "/img/houses/1.jpg",
    "type": "Entire house",
    "town": "Ostuni",
    "title": "Beautiful flat in Ostuni!",
    "rating": 4.93,
    "reviewsCount": 198
  },
  {
    "id": "2",
    "picture": "/img/houses/2.jpg",
    "type": "Entire house",
    "town": "Isla Mujeres",
    "title": "The World Famous Seashell House ~ Casa Caracol",
    "rating": 4.77,
    "reviewsCount": 246
  }
]

Then we’ll use this id to build a unique URL to each house, like /house/1 and /house/2.

Let’s add a link to a house from the components/House.js component, which now has this content:

components/House.js

const House = props => {
  return (
    <div>
      <img src={props.picture} width='100%' alt='House picture' />
      <p>
        {props.type} - {props.town}
      </p>
      <p>{props.title}</p>
      <p>
        {props.rating} ({props.reviewsCount})
      </p>
    </div>
  )
}

export default House

Normally, to add a link we’d use the HTML a tag.

Not in Next.js.

Well, we could use the a tag, but then client-side navigation will not work, and instead we’ll get the normal full page reload when nagivating across different pages. Next.js is great because a site using it will have that immediate feel, that fast feel that we can get using client side rendering.

Let’s import the Link component, the one that can help us with this problem.

components/House.js

import Link from 'next/link'

Then to link to a house, we wrap all inside an a tag, and we wrap this tag inside a Link tag, which will come with 2 attributes, href and as, which determine the page component that will be used and the URL:

components/House.js

const House = props => {
  return (
    <Link href='/houses/[id]' as={'/houses/' + props.id}>
      <a>
        <img src={props.picture} width='100%' alt='House picture' />
        <p>
          {props.type} - {props.town}
        </p>
        <p>{props.title}</p>
        <p>
          {props.rating} ({props.reviewsCount})
        </p>
      </a>
    </Link>
  )
}

export default House

Now houses in the list are a link to the detail page:

Clicking the link won’t get us anywhere right now. We still need to build that detail page URL:

Now, let’s add more information to each house. We’re going to have a detail view, and this detail view will display more information. I am only going to update the first house (we’ll test our interface with that), and we’ll use the second to make sure the house page renders nicely even if we don’t have all the information.

I am going to add the following fields:

  • description contains an HTML string to render as the house description
  • guests the number of guests the house can host
  • bedrooms the number of bedrooms present
  • beds the number of beds
  • baths the number of baths
  • amenities a list of amenities (see below)
  • entirePlace true if the whole house is for yourself (and not a shared place)
  • reviews a list of reviews

amenities is an object with a list of things the house provides, like this:

amenities: {
  wifi: true,
  kitchen: true,
  heating: true,
  freeParking: true
}

We haven’t yet defined the things that a house can have, but we’ll do it later on when we’ll create the host administration part.

Here’s a sample houses.json file:

pages/houses.json

[
  {
    "id": "2329",
    "hostName": "Anna",
    "picture": "/img/houses/1.jpg",
    "type": "Entire house",
    "town": "Ostuni",
    "title": "Beautiful flat in Ostuni!",
    "rating": 4.93,
    "reviewsCount": 198,
    "superhost": true,
    "description": "Spend a unforgettable holiday in the enchanting surroundings of the town of Cisternino (reachable from the near airports of Bari and Brindisi).<br>Trullo Edera offers a heaven of peace and tranquillity, set in an elevated position with a stunning view.<br> It's the perfect place if you like nature. You can stay under an olive tree reading a good book, you can have a walk in the small country streets or go to the nearest beaches.<br> You can even easily visit any of the sights in Apulia such as the caves of Castellana, the trulli of Alberobello, the baroque cities of Lecce and Martina Franca, the excavations of Egnazia, the zoosafari of Fasano, Castel del Monte with Frederick's castle, Grottaglie famous for its ceramics, Taranto, Brindisi and Lecce museums.<br>    Prices vary on period and are to be considered included: in-outcoming cleanings, towels, sheets, water, gas, electricity.",
    "guests": 4,
    "bedrooms": 1,
    "beds": 2,
    "baths": 1,
    "amenities": {
      "wifi": true,
      "kitchen": true,
      "heating": true,
      "freeParking": true
    },
    "entirePlace": true,
    "reviews": [
      {
        "user": "Radhika",
        "date": "August 2019",
        "avatar": "https://a0.muscache.com/im/users/34403074/profile_pic/1432859567/original.jpg?aki_policy=profile_x_medium",
        "comment": "We had an excellent stay at the trullo - everything was perfect, starting with Anna’s generosity to meet us in town so we wouldn’t lose our way, to the beautiful setting of the trullo, to the fresh eggs and tomatoes for our use, to Anna’s tips and suggestions for local"
      }
    ]
  },
  {
    "id": "1523",
    "picture": "/img/houses/2.jpg",
    "type": "Entire house",
    "town": "Isla Mujeres",
    "title": "The World Famous Seashell House ~ Casa Caracol",
    "rating": 4.77,
    "reviewsCount": 246,
    "superhost": false
  }
]

Notice the reviews property - I picked a random review from a real place in Airbnb, and blindly copied the person’s image picture. I will update this with our own imagery and comments later - right now it just serves us for testing.

Let’s now create the template for the single house. We’re going to do so in a file pages/houses/[id].js. The name is a bit weird, right? But there’s a reason. Next.js will use it any time the URL point to that /houses path, and has a path that points to an ID, like /houses/2329.

Create the pages/houses/[id].js file with this content:

pages/houses/[id].js

export default () => (
  <div>
    <p>Test</p>
  </div>
)

Then manually point your browser to open that /houses/2329 URL:

If the number in the URL changes, like /houses/1, it does not matter - the same component will render. This is called a dynamic page.

Now let’s change that component code to a named function, because we need to something.

pages/houses/[id].js

const House = () => (
  <div>
    <p>Test</p>
  </div>
)

export default House

We must attach a function to House, called getInitialProps.

The job of this function is to retrieve the house id, and look it up in the data (our JSON file, at the moment), and return it.

Everything returned from this function will be available as part of the component props.

Let’s start by defining it:

pages/houses/[id].js

House.getInitialProps = () => {

}

This function gets a context object which has the query property. We just need this at the moment, so we can use object destructuring to retrieve it in the parameters:

pages/houses/[id].js

House.getInitialProps = ({ query }) => {
  console.log(query)
}

If you open the browser now, you’ll see an error - Next.js expects this to return an object, so let’s return an empty one:

pages/houses/[id].js

House.getInitialProps = ({ query }) => {
  console.log(query)
  return {}
}

The terminal will now list the id you called the page with, for example { id: '2329' } if the page URL was http://localhost:3000/houses/2329.

Now let’s get the id value, and we can use it to filter the house from the JSON file that holds all the houses data:

pages/houses/[id].js

import houses from '../houses.json'

//...

House.getInitialProps = ({ query }) => {
  const { id } = query

  return {
    house: houses.filter(house => house.id === id)[0]
  }
}

Great! Now we can reference the house object in the component props:

pages/houses/[id].js

const House = props => (
  <div>
    <p>{props.house.title}</p>
  </div>
)

Now we can write the exact same template we used in the list, previously, except every field is now prepended with house.:

pages/houses/[id].js

import houses from '../houses.json'

const House = props => (
  <div>
    <img src={props.house.picture} width='100%' alt='House picture' />
    <p>
      {props.house.type} - {props.house.town}
    </p>
    <p>{props.house.title}</p>
    <p>
      {props.house.rating} ({props.house.reviewsCount})
    </p>
  </div>
)

House.getInitialProps = ({ query }) => {
  const { id } = query

  return {
    house: houses.filter(house => house.id === id)[0]
  }
}

export default House

Here’s the result so far:

Make sure you hit a URL that points to one of the id we added in the JSON, like http://localhost:3000/houses/1523.

You can go to the homepage and then click one of the houses, then click the back button (or the Home link) and click another house, you’ll see both the URL and the page content change.

The thing I want to do now is: improve the template.

First, we’re going to use the house title as the title of the page.

We import the Head component from next/head:

pages/houses/[id].js

import Head from 'next/head'

then inside the component JSX we can include this component with a title tag inside, which will add it to the page head section during rendering:

pages/houses/[id].js

const House = props => (
  <div>
    <Head>
      <title>{props.house.title}</title>
    </Head>
    <img src={props.house.picture} width='100%' alt='House picture' />
    <p>
      {props.house.type} - {props.house.town}
    </p>
    <p>{props.house.title}</p>
    <p>
      {props.house.rating} ({props.house.reviewsCount})
    </p>
  </div>
)

This is the result:

Awesome!

The code for this lesson is available at https://github.com/flaviocopes/airbnb-clone-react-nextjs/commit/c39d2b53729949f2d30aa3604968c3358cff4373


Go to the next lesson