Photo by Miles Burke on Unsplash

Build a Contact Form with React Bootstrap, Express.js & Nodemailer

Kelsey Shiba
5 min readDec 20, 2020

--

I was trying to pull this last piece together for my personal website, and I was trying to integrate all these new pieces of React that I had not quite tested or mastered yet. The information is definitely out there, but it exists in a lot of different places. So, with this blog, I’d like to compile the information here for those who are curious.

The Front End: React with Bootstrap

It has been awesome to learn React with Bootstrap. Documentation for that exists here.

The Form

First we need to import the pieces we need from React Bootstrap:

import { Form, Button } from ‘react-bootstrap’;

Then we create a functional component, returning the pieces that we need. By importing Form, we also have access to Form.Group and Form.Label, etc.

const Contact = () => {     return (
<div>
<h1>Contact</h1><br></br>
<Form>
<Form.Group controlId=”formBasicName”>
<Form.Label>Name</Form.Label>
<Form.Control />
</Form.Group>

<Form.Group controlId=”formBasicEmail”>
<Form.Label>Email</Form.Label>
<Form.Control/>
</Form.Group>
<Form.Group controlId=”formBasicTextField”> <Form.Label>Message</Form.Label>
<Form.Control/>
</Form.Group>
<Button variant=”success” type=”submit”>Submit
</Button>
</Form>
</div>
)
}
export default Contact;

Add State

Now we want to add state, and in this case I wanted to learn hooks. However, those using local state looks like so:

state = {
name: “”,
email: “”,
message: “”
}

Using hooks, here is state:

const initialState = {name: “”, email: “”, message: “”}const [eachEntry, setEachEntry] = useState(initialState)const {name, email, message} = eachEntry

Add Event Handlers

Now let’s add our event handlers. With normal state, we would see a handler like so:

handleOnChange = (event) => {
this.setState({[event.target.name]: event.target.value
})
}

Using hooks, our event handler looks a bit different, but it’s nice to see them side-by-side as they are very similar — and can be built using one function that will change as the user types inputs (instead of creating one per input).

const handleOnChange = e => {
setEachEntry({…eachEntry, [e.target.name]: e.target.value})
}

Add Properties and Event Handlers to Our React Bootstrap Form

I have three inputs for my contact form — name, email, and message. Here’s our first input NAME:

<Form.Control
onChange={handleOnChange}
value={name}
name=’name’
type=”text”
placeholder=”Enter your name” />

Notice that the only big difference here if you were not using hooks would be that the value would be set to this.state.name. I will also paste code if you were to write out this piece using HTML with regular bootstrap so you can see that difference:

<label htmlFor=’name’>Name:</label>
<input
onChange={this.handleOnChange}
type=’text’
name=’name’
value={this.state.name}
className=’form-control’/>

Handle Submit

Now let’s handle the submission of the form. Without hooks, here is the handle submit function:

handleSubmit = (event) => {
event.preventDefault()
this.props.doSomething(this.state)
this.setState({
name: ‘’,
email: ‘’,
message:””
})
}

With hooks, here is the handle submit function, at it’s bare bones:

const handleSubmit = e => {
e.preventDefault()
}

Add a BackEnd to Handle the Submission

Now we need to create a backend for our contact form. There are a lot of different ways to handle this, but in this case I wanted to see if I could do it. I found an article using Express.js and NodeMailer.

Create a new workspace and run:

npm create -y
npm install express — save

Then install NodeMailer:

npm install nodemailer — save

Create a config.js file and input:

module.exports = {
USER: ‘YOUR_EMAIL_ADDRESS’,
PASS: ‘PASSWORD_FOR_EMAIL’
}

I used my Google email and password because I wanted all responses to go there, and there are issues with that that I can discuss later.

Create an index.js file and input:

var express = require(‘express’);
var router = express.Router();
var nodemailer = require(‘nodemailer’);
var cors = require(‘cors’);
const creds = require(‘./config’);
var transport = {
host: ‘smtp.gmail.com’,
port: 587, //I used 465 after reading some of the Google Documentation
auth: {
user: creds.USER,
pass: creds.PASS
}
}
var transporter = nodemailer.createTransport(transport)
transporter.verify((error, success) => {
if (error) {
console.log(error);
} else {
console.log(‘Server is ready to take messages’);
}
});
router.post(‘/send’, (req, res, next) => {
var name = req.body.name
var email = req.body.email
var message = req.body.message
var content = `name: ${name} \n email: ${email} \n message: ${message} `
var mail = {
from: name,
to: ‘RECEIVING_EMAIL_ADDRESS_GOES_HERE’,// ←this was my gmail addy,
subject: ‘New Message from Contact Form’,
text: content
}
transporter.sendMail(mail, (err, data) => {
if (err) {
res.json({ status: ‘fail’ })
} else {
res.json({ status: ‘success’ })
}
})
})
const app = express() app.use(cors())
app.use(express.json())
app.use(‘/’, router)
app.listen(3002)

After these changes are customized, we still need to deal with a few other things.

I created a .gitignore file, and added my config.js file to that, so my passwords won’t not be uploaded for my git commit. The .gitignore file includes:

# Ignore config file with credentials/config.js

To start the server run:

node index.js

Dealing with the Gmail error

When I ran node index.js to start the backend, it gave me a GMAIL error that my user and password was incorrect. After double checking it I realized that it was trying to block my app from signing in. To get it to work — I had to:

  1. Go to my mail.google.com account
  2. Click on my profile circle, click Manage Your Google Account
  3. Click Security on the left
  4. Turn ON Less Secure App Access

Then try running the server again. If it’s connected it will console log “Server is ready to take messages”

Connect the Backend to the Front React App

Now to connect our contact form to the backend. We are sending a post request, creating an alert to let our user know the message was sent, and then resetting the form. Here is the code:

const handleSubmit = e => {
e.preventDefault()
fetch(‘http://localhost:3002/send', {
method: “POST”,
body: JSON.stringify(eachEntry),
headers: {
‘Accept’: ‘application/json’,
‘Content-Type’: ‘application/json’
},
}).then(
(response) => (response.json())
).then((response)=> {
if (response.status === ‘success’) {
alert(“Message Sent.”);
setEachEntry({…eachEntry, [e.target.name]: ‘’})//reset form
} else if(response.status === ‘fail’) {
alert(“Message failed to send.”)
}
})
}

Finally…Test it.

Make sure you start your front end server — go to the new contact page — and send yourself a message to see if it is working.

Further Steps

I definitely would love to see how to make my application work without having to turn on the less secure apps in gmail. I imagine it has to do with setting up OAuth on the backend. That’s another blog for another day.

Code on! ~Kelsey

--

--

Kelsey Shiba

Full Stack Software Engineer, Administrator, Designer, and Musician.