Airbnb clone, build the house detail view

Join the 2022 Full-Stack Web Dev Bootcamp!


This post is part of a new series where we build a clone of Airbnb with Next.js. See the first post here.

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.js file:

houses.js

export default [
  {
    id: 1,
    picture: '/img/1.jpg',
    type: 'Entire house',
    town: 'New York',
    title: 'Beautiful flat in New York!'
  },
  {
    id: 2,
    picture: '/img/2.jpg',
    type: 'Entire house',
    town: 'Amsterdam',
    title: 'A flat in Amsterdam with a great view'
  }
]

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

export default function House(props) {
  return (
    <div>
      <img src={props.picture} width="100%" alt="House picture" />
      <p>
        {props.type} - {props.town}
      </p>
      <p>{props.title}</p>
    </div>
  )
}

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

import Link from 'next/link'

export default function 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>
      </a>
    </Link>
  )
}

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, which now is just a placeholder
  • guests the number of guests the house can host

houses.js

export default [
  {
    id: 1,
    picture: '/img/1.jpg',
    type: 'Entire house',
    town: 'New York',
    title: 'Beautiful flat in New York!',
    description:
      'Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur',
    guests: 4
  },
  {
    id: 2,
    picture: '/img/2.jpg',
    type: 'Entire house',
    town: 'Amsterdam',
    title: 'A flat in Amsterdam with a great view',
    description:
      'Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur',
    guests: 4
  }
]

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/2.

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

pages/houses/[id].js

export default function House() {
  return (
    <div>
      <p>Test</p>
    </div>
  )
}

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

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

Now we must add a function in this file, called getServerSideProps.

The job of this function is to retrieve the house id from the URL, 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

export async function getServerSideProps() {}

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

export async function getServerSideProps({ query }) {
  console.log(query)
}

If you open the browser now, you’ll see an error - Next.js expects this to return an object with a props property, so let’s return it:

pages/houses/[id].js

export async function getServerSideProps({ query }) {
  console.log(query)
  return { props: {} }
}

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.js'

//

export async function getServerSideProps({ query }) {
  const { id } = query

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

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

pages/houses/[id].js

import houses from '../../houses.js'

export default function House(props) {
  return (
    <div>
      <p>{props.house.title}</p>
    </div>
  )
}

export async function getServerSideProps({ query }) {
  const { id } = query

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

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

pages/houses/[id].js

import houses from '../houses.js'

export default function House(props) {
  return (
    <div>
      <img src={props.house.picture} width="100%" alt="House picture" />
      <p>
        {props.house.type} - {props.house.town}
      </p>
      <p>{props.house.title}</p>
    </div>
  )
}

export async function getServerSideProps({ query }) {
  const { id } = query

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

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/2.

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

import Head from 'next/head'
import houses from '../../houses.js'

export default function House(props) {
  return (
    <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>
    </div>
  )
}

export async function getServerSideProps({ query }) {
  const { id } = query

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

This is the result:

Awesome!

See the code on GitHub

Next part: CSS and navigation bar

Want to become a better Web Developer? Join the 2022 Web Development Bootcamp!

⭐️⭐️⭐️ Join the 2022 Web Development Bootcamp ⭐️⭐️⭐️