In this lesson I want to add the ability to add a new house.

In the previous lesson I added a link to the /host/new URL, when we don’t have houses yet.

I am going to also add the link to the components/Header.js file, to make it easy to find, even though in a real app you’d move it somewhere less prominent, I think, as it’s not a functionality that’s used many times:

components/Header.js

<li>
  <Link href='/host/new'>
    <a>Add House</a>
  </Link>
</li>

Now create a new file pages/host/new.js

In this file we’re going to create a form to add a new house.

We start, by adding one house field, title, and we we submit the form to the server, on the host/new server-side route:

import { useState } from 'react'
import Head from 'next/head'

import Layout from '../../components/Layout'

const NewHouse = () => {
  const [title, setTitle] = useState('')

  return (
    <Layout
      content={
        <div>
          <Head>
            <title>Add a new house</title>
          </Head>

          <form
            onSubmit={async event => {
              event.preventDefault()
              try {
                const response = await axios.post('/api/host/new', {
                  house: {
                    title
                  }
                })
                if (response.data.status === 'error') {
                  alert(response.data.message)
                  return
                }

                console.log(response)
                goto('/host')
              } catch (error) {
                alert(error.response.data.message)
                return
              }
            }}>
            <input
              id='title'
              type='text'
              placeholder='House title'
              onChange={event => setTitle(event.target.value)}
            />
            <button>Add house</button>
          </form>

          <style jsx>{``}</style>
        </div>
      }
    />
  )
}

export default NewHouse

We use hooks to store the state of each item in our form.

Now create a new endpoint POST /api/host/new in the server.js file, that uses the Sequelize model to add the house to the database:

server.post('/api/host/new', async (req, res) => {
  const houseData = req.body.house

  if (!req.session.passport) {
    res.writeHead(403, {
      'Content-Type': 'application/json'
    })
    res.end(
      JSON.stringify({
        status: 'error',
        message: 'Unauthorized'
      })
    )

    return
  }

  const userEmail = req.session.passport.user
  User.findOne({ where: { email: userEmail } }).then(user => {
    houseData.host = user.id
    House.create(houseData).then(() => {
      res.writeHead(200, {
        'Content-Type': 'application/json'
      })
      res.end(JSON.stringify({ status: 'success', message: 'ok' }))
    })
  })
})

I basically check if the user is logged in, and then add its id to the house data, before storing it.

If you try it now, in the terminal you’ll get a long list of errors from Sequelize:

Unhandled rejection SequelizeValidationError: notNull Violation: house.picture cannot be null,
notNull Violation: house.type cannot be null,
notNull Violation: house.town cannot be null,
notNull Violation: house.price cannot be null,
notNull Violation: house.superhost cannot be null,
notNull Violation: house.guests cannot be null,
notNull Violation: house.bedrooms cannot be null,
notNull Violation: house.beds cannot be null,
notNull Violation: house.baths cannot be null,
notNull Violation: house.wifi cannot be null,
notNull Violation: house.kitchen cannot be null,
notNull Violation: house.heating cannot be null,
notNull Violation: house.freeParking cannot be null,
notNull Violation: house.entirePlace cannot be null
//...

because we set those fields to be NOT NULL in the database.

Now that we have a basic form set up, let’s add all the fields we need to avoid this error!

import { useState } from 'react'
import Head from 'next/head'
import axios from 'axios'
import Router from 'next/router'

import Layout from '../../components/Layout'

const NewHouse = () => {
  const [title, setTitle] = useState('')
  const [town, setTown] = useState('')
  const [price, setPrice] = useState(0)
  const [picture, setPicture] = useState('')
  const [description, setDescription] = useState('')
  const [guests, setGuests] = useState(0)
  const [bedrooms, setBedrooms] = useState(0)
  const [beds, setBeds] = useState(0)
  const [baths, setBaths] = useState(0)
  const [wifi, setWifi] = useState(false)
  const [kitchen, setKitchen] = useState(false)
  const [heating, setHeating] = useState(false)
  const [freeParking, setFreeParking] = useState(false)
  const [entirePlace, setEntirePlace] = useState(false)
  const [type, setType] = useState('Entire house')

  const houseTypes = ['Entire house', 'Room']

  return (
    <Layout
      content={
        <div>
          <Head>
            <title>Add a new house</title>
          </Head>

          <form
            onSubmit={async event => {
              event.preventDefault()
              try {
                const response = await axios.post('/api/host/new', {
                  house: {
                    title,
                    town,
                    price,
                    picture,
                    description,
                    guests,
                    bedrooms,
                    beds,
                    baths,
                    wifi,
                    kitchen,
                    heating,
                    freeParking,
                    entirePlace,
                    type
                  }
                })
                if (response.data.status === 'error') {
                  alert(response.data.message)
                  return
                }

                Router.push('/host')
              } catch (error) {
                alert(error.response.data.message)
                return
              }
            }}>
            <p>
              <label>House title</label>
              <input
                required
                onChange={event => setTitle(event.target.value)}
                type='text'
                placeholder='House title'
              />
            </p>
            <p>
              <label>Town</label>
              <input
                required
                onChange={event => setTown(event.target.value)}
                type='text'
                placeholder='Town'
              />
            </p>
            <p>
              <label>Price per night</label>
              <input
                required
                onChange={event => setPrice(event.target.value)}
                type='number'
                placeholder='Price per night'
                value={price}
              />
            </p>
            <p>
              <label>House picture URL</label>
              <input
                required
                onChange={event => setPicture(event.target.value)}
                type='text'
                placeholder='House picture URL'
              />
            </p>
            <p>
              <label>House description</label>
              <textarea
                required
                onChange={event =>
                  setDescription(event.target.value)
                }></textarea>
            </p>

            <div>
              <div>
                <p>
                  <label>Number of guests</label>
                  <input
                    required
                    onChange={event => setGuests(event.target.value)}
                    type='number'
                    placeholder='Number of guests'
                    value={guests}
                  />
                </p>
                <p>
                  <label>Number of bedrooms</label>
                  <input
                    required
                    onChange={event => setBedrooms(event.target.value)}
                    type='number'
                    placeholder='Number of bedrooms'
                    value={bedrooms}
                  />
                </p>
                <p>
                  <label>Number of beds</label>
                  <input
                    required
                    onChange={event => setBeds(event.target.value)}
                    type='number'
                    placeholder='Number of beds'
                    value={beds}
                  />
                </p>
                <p>
                  <label>Number of baths</label>
                  <input
                    required
                    onChange={event => setBaths(event.target.value)}
                    type='number'
                    placeholder='Number of baths'
                    value={baths}
                  />
                </p>
              </div>

              <div>
                <p>
                  <label>Does it have Wifi?</label>
                  <select
                    onChange={event => setWifi(event.target.value)}
                    value={wifi}>
                    <option value='true'>Yes</option>
                    <option value='false'>No</option>
                  </select>
                </p>
                <p>
                  <label>Does it have a kitchen?</label>
                  <select
                    onChange={event => setKitchen(event.target.value)}
                    value={kitchen}>
                    <option value='true'>Yes</option>
                    <option value='false'>No</option>
                  </select>
                </p>
                <p>
                  <label>Does it have heating?</label>
                  <select
                    onChange={event => setHeating(event.target.value)}
                    value={heating}>
                    <option value='true'>Yes</option>
                    <option value='false'>No</option>
                  </select>
                </p>
                <p>
                  <label>Does it have free parking?</label>
                  <select
                    onChange={event => setFreeParking(event.target.value)}
                    value={freeParking}>
                    <option value='true'>Yes</option>
                    <option value='false'>No</option>
                  </select>
                </p>
                <p>
                  <label>Is it the entire place?</label>
                  <select
                    onChange={event => setEntirePlace(event.target.value)}
                    value={entirePlace}>
                    <option value='true'>Yes</option>
                    <option value='false'>No</option>
                  </select>
                </p>
                <p>
                  <label>Type of house</label>
                  <select
                    onChange={event => setType(event.target.value)}
                    value={type}>
                    {houseTypes.map((item, key) => (
                      <option value={item} key={key}>
                        {item}
                      </option>
                    ))}
                  </select>
                </p>
              </div>
            </div>

            <button>Add house</button>
          </form>

          <style jsx>{`
            input[type='number'],
            select,
            textarea {
              display: block;
              padding: 20px;
              font-size: 20px !important;
              width: 100%;
              border: 1px solid #ccc;
              border-radius: 4px;
              box-sizing: border-box;
              margin-bottom: 10px;
            }
          `}</style>
        </div>
      }
    />
  )
}

export default NewHouse

I imported the Next.js Router:

import Router from 'next/router'

so that after the form is successfully submitted, we can call Router.push('/host') to redirect the user to the list of houses.

One thing: I noticed the superhost field in the error message:

notNull Violation: house.superhost cannot be null,

We don’t want users to fill this, so we’ll set it as false and then we will edit it manually. If you are not familiar with Airbnb, superhost is a status you get after a number of positive reviews.

We set the default value in models/house.js:

superhost: {
  type: Sequelize.DataTypes.BOOLEAN,
  allowNull: true,
  defaultValue: false
},

I used the houseTypes array in one select to show you how to dynamically add data to a form.

The house picture is a path, either an absolute URL or relative to the base path.

I also set all the fields as required, frontend-side, so we get automatic validation by the browser.

Great! The form should show up nicely now.

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


Go to the next lesson