How To Add an Email Form To Your React Website For Free Using EmailJS and React-Hook-Form

Placido Wang
10 min readSep 29, 2020

A good portfolio website should have a Contact page where visitors can email you. After all, what’s the point of putting in so much work creating your website when visitors have no way of contacting you? Not everyone wants to make a phone call, so having an easy to use email form will make it convenient for them to send you a message. In this tutorial, I will teach you how to create an email form for your React portfolio website with EmailJS and React-Hook-Form.

1. Create A New Project

Let’s create a new React app so you can follow along. Go ahead and navigate to a suitable directory and enter

npx create-react-app my-portfolio

then cd into your project directory and open it in your code editor of choice. Open src/App.js and delete the header tag and its contents. You can also delete or comment out import logo from ‘./logo.svg’; on line 2 since we won’t be using it.

Now, let’s create a Contact component. Create a Contact.js file in src/. For now, let’s just have it as simple as possible.

// Contact.jsimport React from 'react';const Contact = () => {
return (
<div className='contact'>
<h1>Contact</h1>
</div>
);
}
export default Contact;

Now go ahead and import it into App.js.

// App.jsimport React from 'react';
import './App.css';
import Contact from './Contact.js';
function App() {
return (
<div className="App">
<Contact />
</div>
);
}
export default App;

We’ve done a lot. Let’s make sure the Contact component we created is being imported and rendered correctly. In the terminal, run

npm start

and you should see this:

Awesome! Now we know our Contact component is working correctly. On your real website, you would have it conditionally render through something like React Router and Navlinks, but we’re only concerned with the Contact page in this tutorial, so no need to worry about any of that. Now that we have our Contact component, let’s go ahead and fill it out.

2. Create The Form

You can create a regular form in JSX but I like using react-hook-form. It offers several features such as validations and watching for input changes that make it easy to control how our form and web app behave. Go ahead and install it.

npm install react-hook-form

Now, let’s create that email form. Add the following to the main div in Contact.js.

// Contact.js<form id='contact-form'>
<input type='text' name='user_name' placeholder='Name' />
<br/>
<input type='email' name='user_email' placeholder='Email' />
<br/>
<textarea name='message' placeholder='Message'/>
<br/>
<input type='submit' value='Send' />
</form>

We can add a smidge of styling. Create a Contact.css file in src/. Add the following css.

// Contact.cssinput {
width: 600px;
}
textarea {
height: 10em;
width: 600px;
}

So now your page should look like this.

Wild form appeared!

Nice. Let’s integrate some react-hook-form functionality. Import react-hook-form in Contact.js.

// Contact.jsimport { useForm } from 'react-hook-form';

Then, inside our Contact function component, add the following lines before the return statement.

const { register, handleSubmit, watch, errors } = useForm();
const onSubmit = data => console.log(data);

Finally, add the onSubmit event in the form and add ref attributes to each input tag. Our form should look like this.

<form id='contact-form' onSubmit={handleSubmit(onSubmit)}>
<input type='text' name='user_name'
ref={register} placeholder='Name' />
<br/>
<input type='email' name='user_email'
ref={register} placeholder='Email' />
<br/>
<textarea name='message'
ref={register} placeholder='Message'/>
<br/>
<input type='submit' value='Send' />
</form>

Let’s test our react-hook-form by entering data in our form. You can see our data being correctly logged to the console. Hooray!

Notice that our form is handled through the onSubmit function we declared just before the return statement. Here is where we will send the data to our email inbox later.

3. Add Validations To The Form

Now let’s add validations through react-hook-form to make sure our sender is sending a complete email. In the name input, add the following aria attribute and update the ref attribute. Optionally, we can add a maxLength attribute as well.

maxLength='30'
aria-invalid={errors.user_name ? "true" : "false"}
ref={register({ required: true })}/>

We’ll also add a conditionally rendered error message just above the input tag.

{errors.user_name && errors.user_name.type === "required" && (
<div role="alert">Name is required<br/></div>
)}

Your finished name input should look like this.

{errors.user_name && errors.user_name.type === "required" && (
<div role="alert">Name is required<br/></div>
)}
<input
type='text'
name='user_name'
placeholder='Name'
maxLength='30'
aria-invalid={errors.user_name ? "true" : "false"}
ref={register({ required: true })}/>
<br/>

Update the Email input and Message textarea tags in the same way.

Finally, add the following to Contact.css.

// Contact.cssdiv[role='alert'] {
color: red;
margin-top: 1em;
}

Notice now when you try to submit the form with a missing field, you will get an error message. The form data also does not get logged to the console, meaning the onSubmit function does not run. This is good; we only want this function to run when all our validations pass.

Let’s also add a max character length to the message. Give the textarea tag in Contact.js a max length attribute.

maxLength='1500'

We can also take advantage of react-hook-form to display the number of characters left on the page. At the beginning of the Contact function component, declare a new variable that stores the current message, and a variable that keeps track of the remaining character count.

const message = watch('message') || "";
const messageCharsLeft = 1500 - message.length;

Then, add the variable to the form just after the textarea.

<p className='message-chars-left'>{messageCharsLeft}</p>

Lastly, add a bit of styling.

// Contact.css.message-chars-left {
width: 600px;
margin: auto;
text-align: left;
}

Check it out!

Looking great! We’re done with the form for now. Next, we are going to add an email service to our project to make the form functional.

4. Create Your EmailJS Account and Template

We are going to use EmailJS to handle sending the data to your personal inbox. Go to https://www.emailjs.com and create a free account. In the Email Services tab, click Add New Service. I’m using a gmail account so I’m going to choose that. Enter “Contact Form” for the Name and “contact_form” for the Service ID. These can be whatever you wish, as long as they make sense to you. Finish by clicking Add Service. It should be your Default service.

Now let’s create our email template. In the Email Templates tab, click Create New Template. This is how the sent email will appear in your inbox. Let’s change this template to better suit our needs.

Start by replacing the {{ from_name }} variable in the Subject line to {{ user_name }}. The value will come from the form.

Then, at the top of the message, let’s insert a line that reads “Contact Number: {{ contact_number }}”. This will be a randomly generated number that will come from the form for your reference.

Next, replace {{ to_name }} with your name, since this will be going to you and to you only.

Then, you’ll be getting the message from {{ user_name }} so use this in place of the next {{ from_name }}.

Now, inside the quoted section, add {{ user_name }} ({{ user_email }}) on a new line.

You can keep or remove the Best Wishes closing. Lastly, in the Reply To field on the right side, change it to {{ user_email }}. You can leave the From Name field blank.

Your finished template should look like this.

Click Save to save your default template.

5. Add EmailJS To Your Project

In your project code, we’re going to integrate EmailJS. You could load EmailJS in your html head, but I’m going to install EmailJS as a node module.

npm install emailjs-com --save

Now let’s import and initialize EmailJS in Contact.js with your User ID. You can find your User ID in the Integration tab in your EmailJS dashboard.

// Contact.jsimport { init, sendForm } from 'emailjs-com';
init('YOUR_USER_ID');

Let’s also create our method for generating a contact number. Update your React import to include useState and create your state and state setter method. Then, create the method that will generate the contact number.

import React, { useState } from 'react';// ...const Contact = () => {
const [contactNumber, setContactNumber] = useState("000000");

const generateContactNumber = () => {
const numStr = "000000" + (Math.random() * 1000000 | 0);
setContactNumber(numStr.substring(numStr.length - 6));
}
// ...
}

The generateContactNumber() method will set a random 6-digit string of numbers as the contact number. This method ensures the contact number is 6 digits, preserving any leading zeros.

EmailJS sends data from the form’s input values, so we’ll have to add this contact number to our form. This is easily done by adding a hidden input field right in the form.

<input type='hidden' name='contact_number' value={contactNumber} />

Finally, we can add the function that will actually send the data to our email. In the onSubmit function, call the generateContactNumber() method and the sendForm() method, passing in the serviceID, templateID (from your EmailJS dashboard), and your html form id as arguments. We don’t need to pass our EmailJS user ID since we initialized EmailJS already. MAKE SURE you call the generateContactNumber() method INSIDE the onSubmit() method, otherwise your code will enter an infinite loop, setting the contact number, updating the component, and setting the contact number again.

const onSubmit = (data) => {
// console.log(data);
generateContactNumber();
sendForm('default_service', 'YOUR_TEMPLATE_ID', '#contact-form')
.then(function(response) {
console.log('SUCCESS!', response.status, response.text);
}, function(error) {
console.log('FAILED...', error);
});

}

Once you have everything, go ahead and test it!

We got a success message in the console! Let’s check our email!

It worked!! Except, hmm, that doesn’t look quite right. In our test message, we had line breaks between the header, body, and closing in our message, but in the email, all the words have been put into one line. Let’s fix that.

Open your email template in your EmailJS dashboard. Click the Source Code button to open up the html for your template.

In the <p> tag for the message, add this to the inline styling.

white-space: pre-wrap

Save your template. Now let’s send the message again.

Perfect! Notice that the Contact Number also updated, verifying that a new number will generate on each sent message. Keep in mind that you have a monthly limit to the number of emails that EmailJS will send, 200 per month if you are on the free plan. Take this into account when troubleshooting and testing your code.

At this point, your contact form works. However, there are a couple things we can do to really clean it up and make it nicer to use. Let’s start by clearing the form when we submit it. For this, we’ll use good old fashioned JavaScript. Let’s grab the form in the onSubmit() function and reset it when the sendForm() function is successful.

const onSubmit = (data) => {
const form = document.querySelector('#contact-form');
// ...
sendForm(YOUR_PARAMETERS)
.then(function(response) {
// ...
form.reset();
}, ...);
};

Let’s also include a status message that lets the sender know if the message was successfully sent or if it failed. In the beginning of the Contact function component, create a new Hooks state variable.

const [statusMessage, setStatusMessage] = useState("Message");

The initial state “Message” holds space on the page for the message. An empty string would not take up vertical space, and updating the message would shift the page contents around.

Add the status message to the returned JSX.

return (
<div className='contact'>
<h1>Contact</h1>
<p className='status-message'>{statusMessage}</p>

// ...
</div>
);

Now let’s grab that status message in the onSubmit() function and update it when we send the form.

const onSubmit = (data) => {
const statusMessage = document.querySelector('.status-message');
// ...
sendForm(YOUR_PARAMETERS)
.then(function(response) {
// ...
setStatusMessage("Message sent!");
statusMessage.className = "status-message success";
setTimeout(()=> {
statusMessage.className = 'status-message'
}, 5000)

}, function(error) {
// ...
setStatusMessage("Failed to send message! Please try again later.");
statusMessage.className = "status-message failure";
setTimeout(()=> {
statusMessage.className = 'status-message'
}, 5000)

});
}

Don’t forget the styling! Let’s hide the message until we’re ready to show it.

// Contact.css.status-message {
opacity: 0;
}
.success {
opacity: 1;
color: green;
}
.failure {
opacity: 1;
color: red;
}

Let’s see if it works. Turn off your device’s wifi and data, or otherwise disconnect from the internet, and send a test message.

Great, now reconnect to the internet and try again.

Congratulations, your email form is fully functional! Not only do we have validations in place to make sure the sender does not send a message prematurely, we have a toast-style notification message to notify the sender if the message was sent successfully!

This email form clean, responsive, and easy to use. Plus, there’s plenty of room for you to add your own design and functionality. Let me know how helpful you found this tutorial on my own Contact Page.

Happy coding!

--

--