ruby-on-railssendgridsendgrid-rubysendgrid-rails

Getting duplicate emails from contact form in rails/react app


I added a contact form to my rails/react app using Sendgrid. Everything is working, but I'm getting 3-5 emails every time something is submitted through the contact form. Some of them are missing the data from the contact form. I can't spot what in my code is spotting these duplicate emails.

Contact form on the frontend:

import React, { useState } from "react";
import { Redirect } from "react-router-dom";
import _ from "lodash";

import ErrorList from "../utilities/ErrorList";

export const ContactForm = () => {
  let defaultFields = {
    name: "",
    email: "",
    subject: "",
    message: "",
  };

  const [message, setMessage] = useState(defaultFields);
  const [errors, setErrors] = useState({});
  const [successMessage, setSuccessMessage] = useState();

  if (successMessage) {
    return <Redirect to="/success" />;
  }

  const newMessage = (formData) => {
    fetch("/api/v1/contact", {
      method: "POST",
      body: JSON.stringify(formData),
      credentials: "same-origin",
      headers: {
        Accept: "application/json",
        "Content-Type": "application/json",
      },
    })
      .then((response) => {
        if (response.ok) {
          return response;
        } else {
          let errorMessage = `${response.status} (${response.statusText})`,
            error = new Error(errorMessage);
          throw error;
        }
      })
      .then((response) => response.json())
      .then((body) => {
        if (body.sent) {
          setSuccessMessage(true);
        } else {
          setSuccessMessage(false);
        }
      })
      .catch((error) => console.error(`Error in fetch: ${error.message}`));
  };

  const handleChange = (event) => {
    setMessage({
      ...message,
      [event.currentTarget.name]: event.currentTarget.value,
    });
  };

  const validForSubmission = () => {
    let submitErrors = {};
    const requiredFields = ["name", "email", "subject", "message"];
    requiredFields.forEach((field) => {
      if (
        message[field].trim() === "" ||
        message[field].trim() === "" ||
        message[field].trim() === "" ||
        message[field].trim() === ""
      ) {
        submitErrors = {
          ...submitErrors,
          [field]: "is blank",
        };
      }
    });
    setErrors(submitErrors);
    return _.isEmpty(submitErrors);
  };

  const handleSubmit = (event) => {
    event.preventDefault();
    if (validForSubmission()) {
      newMessage(message);
      newMessage(defaultFields);
    }
  };

  return (
    <section className="section">
      <div className="container">
        <form onSubmit={handleSubmit} className="new-post-form callout">
          <ErrorList errors={errors} />
          <div className="field">
            <label className="label">
              Name:
              <div className="control">
                <input
                  className="input"
                  type="text"
                  placeholder="Please enter your name"
                  name="name"
                  id="name"
                  onChange={handleChange}
                  value={message.name}
                />
              </div>
            </label>
          </div>

          <div className="field">
            <label className="label">
              Email:
              <div className="control">
                <input
                  className="input"
                  type="text"
                  placeholder="Please enter your email address"
                  name="email"
                  id="email"
                  onChange={handleChange}
                  value={message.email}
                />
              </div>
            </label>
          </div>

          <div className="field">
            <label className="label">
              Subject:
              <div className="control">
                <input
                  className="input"
                  type="text"
                  placeholder="Please add a message subject"
                  name="subject"
                  id="subject"
                  onChange={handleChange}
                  value={message.subject}
                />
              </div>
            </label>
          </div>

          <div className="field">
            <label className="label">
              Message:
              <div className="control">
                <textarea
                  className="textarea"
                  placeholder="Add your message here"
                  name="message"
                  id="message"
                  onChange={handleChange}
                  value={message.message}
                ></textarea>
              </div>
            </label>
          </div>

          <div className="field is-grouped">
            <div className="control">
              <button className="button is-link">Submit</button>
            </div>
            <div className="control">
              <button className="button is-link is-light">Cancel</button>
            </div>
          </div>
        </form>
        {successMessage === false && (
          <div className="blog-flex">
            <p>Sorry your message couldn&apos;t be sent. Please try again and make sure all fields are filled out.</p>
          </div>
        )}
      </div>
    </section>
  );
};

export default ContactForm;

Submitted data from the contact form hits the contact_controller

require 'pry'

class Api::V1::ContactController < ApiController

  before_action :contact_params, only: [:create]

  def create
    @message = contact_params
    ContactForm.send_message_email(@message).deliver
    
    if ContactForm.send_message_email(@message).deliver
      render json: {sent: true}
    else
      render json: {sent: false}
    end
  end
  
  protected

  def contact_params
    params.require(:contact).permit([:name, :email, :subject, :message])
  end

end

ContactForm in app/mailers

require 'pry'

class ContactForm < ApplicationMailer

  default :from => 'info@maddoxgreyauthor.com'

  # send email from contact form
  def send_message_email(message)
    @message = message
    mail( :to => 'info@maddoxgreyauthor.com',
    :subject => 'New Message From Maddox Grey Author' )
  end
end

And finally, the layout for the email. This is in app/views/contact_forms/send_message_email.html.erb

<!DOCTYPE html>
<html>
  <head>
    <meta content='text/html; charset=UTF-8' http-equiv='Content-Type' />
  </head>
  <body>
    <h1>New message received from <%= @message["name"] %>!</h1>
    <p>Email: <%= @message["email"] %></p>
    <p>Subject: <%= @message["subject"] %></p>
    <p>Message: <%= @message["message"] %></p>
  </body>
</html>

Solution

  • Here you're sending the email twice:

      def create
        @message = contact_params
        ContactForm.send_message_email(@message).deliver #<--- once
        
        if ContactForm.send_message_email(@message).deliver #<--- twice
          render json: {sent: true}
        else
          render json: {sent: false}
        end
      end
    

    Doing the following should help at least with one of the duplicate emails:

      def create
        @message = contact_params
        send_email = ContactForm.send_message_email(@message).deliver 
        
        if send_email 
          render json: {sent: true}
        else
          render json: {sent: false}
        end
      end
    

    Or a more space effective solution:

      def create
          render json: {sent: !!ContactForm.send_message_email(contact_params).deliver}
      end
    

    Secondly, here you're also calling the API function twice:

      const handleSubmit = (event) => {
        event.preventDefault();
        if (validForSubmission()) {
          newMessage(message); //<-- Once
          newMessage(defaultFields); //<-- Twice
        }
      };