Airbnb clone, show the price for the chosen dates

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.

Once the person that wants to book chooses a start date and an end date, we can calculate the cost of the stay.

Let’s keep things simple and just show a fixed price, without additional taxes and fees that the real Airbnb might factor in.

Also, we’ll keep the price equal for all days, and we’ll not have any discount for longer stays.

The price refers to the whole booking, with no additional bonus for multiple guests.

We’ll work on those things later.

Let’s store the price in the houses.js file, after each title field:

export default [
  {
    id: 1,
    picture: '/img/1.jpg',
    type: 'Entire house',
    town: 'New York',
    title: 'Beautiful flat in New York!',
    price: '150.00',
    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',
    price: '90.00',
    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
  }
]

Now that we have the price information, we can present the information in the pages/houses/[id].js template.

The DateRangePicker.js component handles the startDate and endDate information. When those things change, we want to alert the parent component.

How?

We pass a function as a prop, and we call this function when they change.

So, we first assume that we have a datesChanged function passed as a prop to our DateRangePicker.js component:

components/DateRangePicker.js

export default function DateRangePicker({ datesChanged }) {
  //...
}

then we call this function, passing the start date and the end date for the stay of the user. Where do we call it? Well, we have 2 places where we do call setStartDate() and/or setEndDate(), and right after we do this call, we’ll call datesChanged().

We need to pay attention though: when we call setStartDate(), which is our update function from the hook const [startDate, setStartDate] = useState(today), we don’t immediately have access to the new startDate value - that’s only going to happen in the next rerender.

So when we call datesChanged(), we need to pass the same value we passed to setStartDate(). Same for setEndDate() of course.

Otherwise we’d reference the old value of the start/end date.

So basically in the first DayPickerInput onDayChange prop function becomes:

onDayChange={day => {
  setStartDate(day)
  const newEndDate = new Date(day)
  if (numberOfNightsBetweenDates(day, endDate) < 1) {
    newEndDate.setDate(newEndDate.getDate() + 1)
    setEndDate(newEndDate)
  }
  datesChanged(day, newEndDate)
}}

Notice that I moved const newEndDate = new Date(day) outside of the if block, so we can access it outside of it, after the block ends.

In the second DayPickerInput, onDayChange becomes:

onDayChange={day => {
  setEndDate(day)
  datesChanged(startDate, day)
}}

Great!

Now in pages/houses/[id].js, change the DateRangePicker component in the JSX and add a datesChanged prop, with a function as its value:

pages/houses/[id].js

<DateRangePicker />

becomes:

pages/houses/[id].js

<DateRangePicker
  datesChanged={(startDate, endDate) => {
    console.log(startDate, endDate)
  }}
/>

We just log the startDate and endDate values, and you can try it. Try changing the start or end date, and every time you change something, you should see it logged in the DevTools:

In the pages/houses/[id].js component, let’s use useState to create a new dateChosen state value. Remember that when we use hooks, we always need to define them in the body of the component.

Import useState from React:

import { useState } from 'react'

Before the return line, inside the component, we add:

const [dateChosen, setDateChosen] = useState(false)

to create this new dateChosen state property, which defaults to false:

export default function House(props) {
  const [dateChosen, setDateChosen] = useState(false)

  return <Layout>{/* ... */}</Layout>
}

Now inside the component JSX we can call setDateChosen() to update the value to true when datesChanged() is invoked inside DateRangePicker:

<DateRangePicker
  datesChanged={(startDate, endDate) => {
    console.log(startDate, endDate)
    setDateChosen(true)
  }}
/>

Let’s now show the price for the total stay. I want to count the number of nights that a person had to pay to rent a house and sleep in it, depending on the checkin date, and the checkout date.

I looked at different solutions, and the one that gave me the least problems, considering all the issues with dates (including DST), was this: starting from the starting date, we add one day until the date represents a date after the end date.

I first clone the dates we are given, because dates are objects, and we get a reference to that object. If we forget this, using setDate() in the function would also affect the variable outside of this function, which would change the date in the datepicker!

Here’s the code:

const calcNumberOfNightsBetweenDates = (startDate, endDate) => {
  const start = new Date(startDate) //clone
  const end = new Date(endDate) //clone
  let dayCount = 0

  while (end > start) {
    dayCount++
    start.setDate(start.getDate() + 1)
  }

  return dayCount
}

I define a new component state property using:

const [numberOfNightsBetweenDates, setNumberOfNightsBetweenDates] = useState(0)

and we call setNumberOfNightsBetweenDates() inside the datesChanged prop of DateRangePicker:

<DateRangePicker
  datesChanged={(startDate, endDate) => {
    setNumberOfNightsBetweenDates(
      calcNumberOfNightsBetweenDates(startDate, endDate)
    )
    setDateChosen(true)
  }}
/>

Great, now we have the number of days we want to book in numberOfNightsBetweenDates, and I’m going to use it to calculate the cost, to display it to the user using conditional rendering:

{
  dateChosen && (
    <div>
      <h2>Price per night</h2>
      <p>${props.house.price}</p>
      <h2>Total price for booking</h2>
      <p>${(numberOfNightsBetweenDates * props.house.price).toFixed(2)}</p>
    </div>
  )
}

That’s the result we should have right now:

We can also add a new button at the end:

{
  dateChosen && (
    <div>
      <h2>Price per night</h2>
      <p>${props.house.price}</p>
      <h2>Total price for booking</h2>
      <p>${(numberOfNightsBetweenDates * props.house.price).toFixed(2)}</p>
      <button className="reserve">Reserve</button>
    </div>
  )
}

and let’s style it a bit in the CSS below:

button {
  background-color: rgb(255, 90, 95);
  color: white;
  font-size: 13px;
  width: 100%;
  border: none;
  height: 40px;
  border-radius: 4px;
  cursor: pointer;
}

Once people click the “Reserve” button, we are soon going to do 2 things based on the logged in state.

If you’re logged in, we’ll go on with the registration process, and if you are not logged in, we’re going through the login process.

I know, we don’t have a login management process, yet!

That’s what we’re going to do in the next lessons.

See the code on GitHub

Next part: Login and signup forms

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

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