Bjorn Krols
June 19 2019

Building a serverless React contact form

In this short beginner-friendly tutorial you'll learn how to build a serverless React contact form.

If you are building a static website with React or Gatsby, setting up a server just for the sake of hooking up a contact form may be overkill.

In this tutorial, you'll be integrating a Form to Email service such as Formspark, Basin, Formkeep or Pageclip instead.

Check out this link if you wish to build your own Form to Email service from scratch.

Prerequisites

  • A code editor such as Visual Studio Code or Atom.
  • A React project, you can generate one using create-react-app.
  • A Form to Email service account, this tutorial uses Formspark, alternatives include Basin, Formkeep, Formspree and Pageclip.

Initial state

Initialize the state with your form's default input field values.

this.state = {
  name: '',
  email: '',
  message: ''
}

💡 You can prefill certain fields here to make your user's life easier.

📖 React: State and Lifecycle

Render function

You'll override the form's default behavior by attaching an onSubmit event listener.

Ensure every input field has a unique name attribute, this is a requirement for most Form to Email services and will also enable you to use a generic onChange event listener.

Add a button of type submit.

render() {
    return (
      <form onSubmit={this.onSubmit}>
        <div>
          <label htmlFor="name">Name:</label>
          <input
            type="text"
            name="name"
            value={this.state.name}
            onChange={this.onChange}
          />
        </div>
        <div>
          <label htmlFor="email">Email:</label>
          <input
            type="email"
            name="email"
            value={this.state.email}
            onChange={this.onChange}
          />
        </div>
        <div>
          <label>Message:</label>
          <textarea
            name="message"
            value={this.state.message}
            onChange={this.onChange}
          />
        </div>
        <button type="submit">Submit</button>
      </form>
    )
  }

💡 If you wish to bind a label element to a form field in React you must use the htmlFor attribute instead of the for attribute.

📖 React: render()

Input change handler

To ensure your state is updated when the user changes an input field you must listen for onChange events on the input fields.

If you use a unique name attribute for each input field, you can use the following generic / dynamic onChange event listener:

  onChange(event) {
    const target = event.target
    const name = target.name
    const value = target.value
    this.setState({
      [name]: value
    })
  }

📖 React: Forms

Submit handler

This step is optional but overriding your form's default onSubmit behavior and using AJAX to submit data instead enables you to keep full control over the user flow.

You choose where your user is redirected (if at all), what to show when the request is loading, succeeds or fails, how to reset the form after submission, etc...

The implementation below uses fetch but Axios and JavaScript XHR work just as well.

  onSubmit(event) {
    event.preventDefault()
    fetch('https://submit-form.com/your-form-id', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        Accept: 'application/json'
      },
      body: JSON.stringify(this.state)
    })
      .then(function(response) {
        console.log(response)
      })
      .catch(function(error) {
        console.error(error)
      })
  }

💡 The Content-Type entity header is used to indicate the media type of the resource: "I am sending you JSON data".

📖 MDN: Content-Type

💡 The Accept request HTTP header advertises which content types, expressed as MIME types, the client is able to understand: "I am expecting you to respond with JSON data"

📖 MDN: Accept

End result

import React, { Component } from 'react'

class ContactForm extends Component {
  constructor(props) {
    super(props)
    this.state = {
      name: '',
      email: '',
      message: ''
    }
    this.onChange = this.onChange.bind(this)
    this.onSubmit = this.onSubmit.bind(this)
  }
  onChange(event) {
    const target = event.target
    const name = target.name
    const value = target.value
    this.setState({
      [name]: value
    })
  }
  onSubmit(event) {
    event.preventDefault()
    fetch('https://submit-form.com/your-form-id', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        Accept: 'application/json'
      },
      body: JSON.stringify(this.state)
    })
      .then(function(response) {
        console.log(response)
      })
      .catch(function(error) {
        console.error(error)
      })
  }
  render() {
    return (
      <form onSubmit={this.onSubmit}>
        <div>
          <label htmlFor="name">Name:</label>
          <input
            type="text"
            name="name"
            value={this.state.name}
            onChange={this.onChange}
          />
        </div>
        <div>
          <label htmlFor="email">Email:</label>
          <input
            type="email"
            name="email"
            value={this.state.email}
            onChange={this.onChange}
          />
        </div>
        <div>
          <label>Message:</label>
          <textarea
            name="message"
            value={this.state.message}
            onChange={this.onChange}
          />
        </div>
        <button type="submit">Submit</button>
      </form>
    )
  }
}
export default ContactForm

Get in touch