Do you want to build a functional contact form for your React application?
Whether you're building a portfolio website, business landing page, or complex web application, contact forms are essential for user engagement.
In this comprehensive guide, you'll learn how to create a contact form in React.js. I'll also show you how to implement email notification functionality using an email service provider.
Table of Contents
- How to Create a Contact Form in React.js
- Troubleshooting Common Errors
- Best Practices and Security
- Frequently Asked Questions
How to Create a Contact Form in React.js
Prerequisites
Before we dive in, make sure you have these requirements set up:
- Node.js installed on your machine (version 20 or higher). Download the latest version here
- Basic knowledge of JavaScript and React.js
- A code editor like Visual Studio Code
- An email service provider. I'll use SendLayer for this tutorial. You can get started with 200 free emails
Start your free trial at SendLayer
After creating your SendLayer account, make sure to authorize your sending domain. This step is crucial for improving email deliverability and ensuring your messages reach the inbox instead of the spam folder.
Step 1: Setting Up Your React Project
If you already have a React project, you can skip this section. Otherwise, let's create a new React app using Vite.
Open your terminal and run the following command:
npm create vite@latest contact-form-app -- --template react
Once the installation completes, navigate to the project directory:
cd contact-form-app
Install the project dependencies:
npm install
Start the development server:
npm run dev
You should see your React app running at http://localhost:5173. Now we're ready to build our contact form!
Step 2: Building the Contact Form Component
Let's create a clean, accessible contact form component. Create a new file called ContactForm.jsx in the src directory.
Within this file, add the following code:
import { useState } from 'react';
import './ContactForm.css';
function ContactForm() {
const [formData, setFormData] = useState({
name: '',
email: '',
subject: '',
message: ''
});
const handleChange = (e) => {
const { name, value } = e.target;
setFormData(prevState => ({
...prevState,
[name]: value
}));
};
const handleSubmit = (e) => {
e.preventDefault();
// We'll implement this later
console.log('Form submitted:', formData);
// Reset the form
setFormData({
name: '',
email: '',
subject: '',
message: ''
});
};
return (
<div className="contact-form-container">
<h2>Contact Us</h2>
<form onSubmit={handleSubmit} className="contact-form">
<div className="form-group">
<label htmlFor="name">Name *</label>
<input
type="text"
id="name"
name="name"
value={formData.name}
onChange={handleChange}
required
placeholder="Your full name"
/>
</div>
<div className="form-group">
<label htmlFor="email">Email *</label>
<input
type="email"
id="email"
name="email"
value={formData.email}
onChange={handleChange}
required
placeholder="your.email@example.com"
/>
</div>
<div className="form-group">
<label htmlFor="subject">Subject *</label>
<input
type="text"
id="subject"
name="subject"
value={formData.subject}
onChange={handleChange}
required
placeholder="What's this about?"
/>
</div>
<div className="form-group">
<label htmlFor="message">Message *</label>
<textarea
id="message"
name="message"
value={formData.message}
onChange={handleChange}
required
rows="6"
placeholder="Tell us more about your inquiry..."
/>
</div>
<button type="submit" className="submit-btn">
Send Message
</button>
</form>
</div>
);
}
export default ContactForm;
Now, let's add some basic styling. Create a ContactForm.css file:
.contact-form-container {
max-width: 800px;
margin: 2rem auto;
padding: 2rem 3rem;
background: #f9f9f9;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}
.contact-form {
display: flex;
flex-direction: column;
gap: 1rem;
width: 500px;
}
.contact-form h2 {
text-align: center;
color: #333;
margin-bottom: 2rem;
}
.form-group {
margin-bottom: 1.5rem;
}
.form-group label {
display: block;
width: 100%;
text-align: left;
margin-bottom: 0.5rem;
color: #555;
font-weight: 500;
}
.form-group input,
.form-group textarea {
width: 100%;
padding: 0.75rem 0.3rem;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 1rem;
transition: border-color 0.3s ease;
}
.form-group input:focus,
.form-group textarea:focus {
outline: none;
border-color: #007bff;
box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25);
}
.submit-btn {
width: 100%;
padding: 1rem;
background: #007bff;
color: white;
border: none;
border-radius: 4px;
font-size: 1.1rem;
cursor: pointer;
transition: background-color 0.3s ease;
}
.submit-btn:hover {
background: #0056b3;
}
.submit-btn:disabled {
background: #6c757d;
cursor: not-allowed;
}
Import and use the component in App.jsx:
import ContactForm from './ContactForm';
import './App.css';
function App() {
return (
<div className="App">
<h1>My Website</h1>
<ContactForm />
</div>
);
}
export default App;
Congratulations! You've created a contact form for your React application. Let's run the development server to test our form:
npm run dev
Copy the local address and open it in a browser. You should see the contact form component visible on the page.
With the current implementation, the form data will be logged to the console upon submission.
In the next section, I'll implement email notification functionality to alert site owners whenever a user fills the contact form.
Step 3: Handling Form State and Validation
Now let's add proper form validation and error handling to our component. Update your ContactForm.jsx:
import { useState } from 'react';
import './ContactForm.css';
function ContactForm() {
const [formData, setFormData] = useState({
name: '',
email: '',
subject: '',
message: ''
});
const [errors, setErrors] = useState({});
const [isSubmitting, setIsSubmitting] = useState(false);
const [submitStatus, setSubmitStatus] = useState('');
const validateEmail = (email) => {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email);
};
const validateForm = () => {
const newErrors = {};
if (!formData.name.trim()) {
newErrors.name = 'Name is required';
} else if (formData.name.trim().length < 2) {
newErrors.name = 'Name must be at least 2 characters';
}
if (!formData.email.trim()) {
newErrors.email = 'Email is required';
} else if (!validateEmail(formData.email)) {
newErrors.email = 'Please enter a valid email address';
}
if (!formData.subject.trim()) {
newErrors.subject = 'Subject is required';
} else if (formData.subject.trim().length < 5) {
newErrors.subject = 'Subject must be at least 5 characters';
}
if (!formData.message.trim()) {
newErrors.message = 'Message is required';
} else if (formData.message.trim().length < 10) {
newErrors.message = 'Message must be at least 10 characters';
}
return newErrors;
};
const handleChange = (e) => {
const { name, value } = e.target;
setFormData(prevState => ({
...prevState,
[name]: value
}));
// Clear errors as user types
if (errors[name]) {
setErrors(prevErrors => ({
...prevErrors,
[name]: ''
}));
}
};
const handleSubmit = async (e) => {
e.preventDefault();
const formErrors = validateForm();
if (Object.keys(formErrors).length > 0) {
setErrors(formErrors);
return;
}
setIsSubmitting(true);
setErrors({});
setSubmitStatus('');
try {
// We'll implement the API call here
console.log('Sending form data:', formData);
// Simulate API call for now
await new Promise(resolve => setTimeout(resolve, 2000));
setSubmitStatus('success');
setFormData({
name: '',
email: '',
subject: '',
message: ''
});
} catch (error) {
console.error('Error submitting form:', error);
setSubmitStatus('error');
} finally {
setIsSubmitting(false);
}
};
return (
<div className="contact-form-container">
<h2>Contact Us</h2>
{submitStatus === 'success' && (
<div className="success-message">
Thank you! Your message has been sent successfully.
</div>
)}
{submitStatus === 'error' && (
<div className="error-message">
Sorry, there was an error sending your message. Please try again.
</div>
)}
<form onSubmit={handleSubmit} className="contact-form">
<div className="form-group">
<label htmlFor="name">Name *</label>
<input
type="text"
id="name"
name="name"
value={formData.name}
onChange={handleChange}
required
placeholder="Your full name"
className={errors.name ? 'error' : ''}
/>
{errors.name && <span className="error-text">{errors.name}</span>}
</div>
<div className="form-group">
<label htmlFor="email">Email *</label>
<input
type="email"
id="email"
name="email"
value={formData.email}
onChange={handleChange}
required
placeholder="your.email@example.com"
className={errors.email ? 'error' : ''}
/>
{errors.email && <span className="error-text">{errors.email}</span>}
</div>
<div className="form-group">
<label htmlFor="subject">Subject *</label>
<input
type="text"
id="subject"
name="subject"
value={formData.subject}
onChange={handleChange}
required
placeholder="What's this about?"
className={errors.subject ? 'error' : ''}
/>
{errors.subject && <span className="error-text">{errors.subject}</span>}
</div>
<div className="form-group">
<label htmlFor="message">Message *</label>
<textarea
id="message"
name="message"
value={formData.message}
onChange={handleChange}
required
rows="6"
placeholder="Tell us more about your inquiry..."
className={errors.message ? 'error' : ''}
/>
{errors.message && <span className="error-text">{errors.message}</span>}
</div>
<button type="submit" className="submit-btn" disabled={isSubmitting}>
{isSubmitting ? 'Sending...' : 'Send Message'}
</button>
</form>
</div>
);
}
export default ContactForm;
Add these styles to your ContactForm.css:
.success-message {
background: #d4edda;
color: #155724;
padding: 1rem;
border-radius: 4px;
border: 1px solid #c3e6cb;
margin-bottom: 1.5rem;
}
.error-message {
background: #f8d7da;
color: #721c24;
padding: 1rem;
border-radius: 4px;
border: 1px solid #f5c6cb;
margin-bottom: 1.5rem;
}
.form-group input.error,
.form-group textarea.error {
border-color: #dc3545;
box-shadow: 0 0 0 2px rgba(220, 53, 69, 0.25);
}
.error-text {
color: #dc3545;
font-size: 0.875rem;
margin-top: 0.25rem;
display: block;
}
Now we've added form validation to the contact form. When filling it out, users will see an error when fields aren't properly filled.
Step 4: Creating the Backend Server
Since React runs in the browser, we need a backend server to handle email sending. You can connect the form to a backend server using SMTP connection or through an email API.
For this tutorial, I'll use an email API. Let's create an Express.js server that will process our form submissions.
First, create a new directory for your backend:
mkdir backend && cd backend
Initialize a new Node.js project:
npm init -y
Install the required dependencies:
npm install express cors dotenv sendlayer
Install the Node.js dev server package:
npm install -D nodemon
Once the installation completes, create a server.js file in your backend directory:
const express = require('express');
const cors = require('cors');
const dotenv = require('dotenv');
const { SendLayer } = require('sendlayer');
dotenv.config();
const app = express();
const port = process.env.PORT || 3001;
// Initialize SendLayer
const sendlayer = new SendLayer(process.env.SENDLAYER_API_KEY);
// Middleware
app.use(cors());
app.use(express.json());
// Contact form endpoint
app.post('/api/contact', async (req, res) => {
try {
const { name, email, subject, message } = req.body;
// Validate required fields
if (!name || !email || !subject || !message) {
return res.status(400).json({
error: 'All fields are required'
});
}
// Send notification email to yourself
const notificationResponse = await sendlayer.Emails.send({
from: {
email: process.env.FROM_EMAIL,
name: 'Your Website'
},
to: [{
email: process.env.FROM_EMAIL,
name: 'Website Owner'
}],
subject: `New Contact Form: ${subject}`,
html: `
<div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto;">
<h2 style="color: #333; border-bottom: 2px solid #007bff; padding-bottom: 10px;">
New Contact Form Submission
</h2>
<div style="background: #f9f9f9; padding: 20px; border-radius: 8px; margin: 20px 0;">
<p><strong>Name:</strong> ${name}</p>
<p><strong>Email:</strong> <a href="mailto:${email}">${email}</a></p>
<p><strong>Subject:</strong> ${subject}</p>
</div>
<div style="background: white; padding: 20px; border-left: 4px solid #007bff;">
<h3>Message:</h3>
<p style="line-height: 1.6;">${message.replace(/\n/g, '<br>')}</p>
</div>
<div style="margin-top: 20px; padding: 15px; background: #e9ecef; border-radius: 4px;">
<p style="margin: 0; font-size: 14px; color: #6c757d;">
<em>To reply directly, send an email to: ${email}</em>
</p>
</div>
</div>
`,
text: `
New Contact Form Submission
Name: ${name}
Email: ${email}
Subject: ${subject}
Message:
${message}
Reply to: ${email}
`,
replyTo: email,
tags: ['contact-form'],
});
// Send auto-reply to the user
const autoReplyResponse = await sendlayer.Emails.send({
from: {
email: process.env.FROM_EMAIL,
name: 'Your Website'
},
to: [{
email: email,
name: name
}],
subject: `Thank you for contacting us, ${name}!`,
html: `
<div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto;">
<div style="background: linear-gradient(135deg, #007bff, #0056b3); color: white; padding: 30px; text-align: center; border-radius: 8px 8px 0 0;">
<h1 style="margin: 0; font-size: 28px;">Thank You!</h1>
<p style="margin: 10px 0 0 0; font-size: 16px; opacity: 0.9;">
We've received your message
</p>
</div>
<div style="background: white; padding: 30px; border-radius: 0 0 8px 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1);">
<p style="font-size: 16px; color: #333; line-height: 1.6;">
Hi <strong>${name}</strong>,
</p>
<p style="font-size: 16px; color: #333; line-height: 1.6;">
Thank you for reaching out! We've received your message about "<em>${subject}</em>"
and will get back to you as soon as possible.
</p>
<div style="background: #f8f9fa; padding: 20px; border-radius: 6px; margin: 25px 0; border-left: 4px solid #007bff;">
<h3 style="margin: 0 0 10px 0; color: #333; font-size: 18px;">Your Message:</h3>
<p style="margin: 0; font-style: italic; color: #555; line-height: 1.6;">
"${message.replace(/\n/g, '<br>')}"
</p>
</div>
<p style="font-size: 16px; color: #333; line-height: 1.6;">
We typically respond within 24 hours during business days. If you have any urgent questions,
feel free to reply directly to this email.
</p>
<div style="margin: 30px 0; padding: 20px; background: #e3f2fd; border-radius: 6px; text-align: center;">
<p style="margin: 0; color: #1976d2; font-weight: 500;">
Best regards,<br>
<strong>Your Website Team</strong>
</p>
</div>
</div>
</div>
`,
text: `
Hi ${name},
Thank you for reaching out! We've received your message about "${subject}" and will get back to you as soon as possible.
Your Message:
"${message}"
We typically respond within 24 hours during business days.
Best regards,
Your Website Team
`,
tags: ['auto-reply', 'contact-form'],
});
console.log('Emails sent successfully:', {
notification: notificationResponse,
autoReply: autoReplyResponse
});
res.json({
success: true,
message: 'Your message has been sent successfully!'
});
} catch (error) {
console.error('Error sending email:', error);
res.status(500).json({
error: 'Failed to send message. Please try again later.'
});
}
});
app.get('/health', (req, res) => {
res.json({ status: 'Server is running' });
});
app.listen(port, () => {
console.log(`Server running on port ${port}`);
});
Update your package.json scripts:
{
"scripts": {
"start": "node server.js",
"dev": "nodemon server.js"
}
}
Create a .env file for your environment variables:
PORT=3001
NODE_ENV=development
# SendLayer API Configuration
SENDLAYER_API_KEY=your-sendlayer-api-key
FROM_EMAIL=noreply@yourdomain.com
Pro Tip: Never commit sensitive credentials like API keys to version control platforms like GitHub. Always use environment variables to secure your details.
To get your API key, log in to your SendLayer account. Navigate to Settings » API Keys.
Click the copy icon next to Default API key to copy it.
Tip: You can create new API keys by clicking the Create New API Key button.
Code Breakdown
In the code above, we initialize the SendLayer package with the API key variable stored in the environment variable.
Then, I updated the /api/contact endpoint to include the email logic. The route requires 4 parameters (name, email, subject, and message). These parameters will be passed from the contact form when we connect the backend server to the form component.
Within the route, I created 2 constants that call the sendlayer.Emails.send() method:
-
notificationResponse: Sends a notification email to the site owner -
autoReplyResponse: Sends an auto-reply email to the user
SendLayer's SDK enables you to send both HTML and plain-text emails. In our implementation, I added both options to ensure the email content is received.
Tip: See our developer documentation to learn more about sending emails with the SendLayer API.
Step 5: Connecting the Backend Server to the Contact Form
Now let's connect our React frontend to the backend. Install axios for making HTTP requests.
Navigate to the React project's directory and run:
npm install axios
Update your ContactForm.jsx file to make actual API calls:
import { useState } from 'react';
import axios from 'axios';
import './ContactForm.css';
const API_BASE_URL = 'http://localhost:3001';
function ContactForm() {
const [formData, setFormData] = useState({
name: '',
email: '',
subject: '',
message: ''
});
const [errors, setErrors] = useState({});
const [isSubmitting, setIsSubmitting] = useState(false);
const [submitStatus, setSubmitStatus] = useState('');
const validateEmail = (email) => {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email);
};
const validateForm = () => {
const newErrors = {};
if (!formData.name.trim()) {
newErrors.name = 'Name is required';
} else if (formData.name.trim().length < 2) {
newErrors.name = 'Name must be at least 2 characters';
}
if (!formData.email.trim()) {
newErrors.email = 'Email is required';
} else if (!validateEmail(formData.email)) {
newErrors.email = 'Please enter a valid email address';
}
if (!formData.subject.trim()) {
newErrors.subject = 'Subject is required';
} else if (formData.subject.trim().length < 5) {
newErrors.subject = 'Subject must be at least 5 characters';
}
if (!formData.message.trim()) {
newErrors.message = 'Message is required';
} else if (formData.message.trim().length < 10) {
newErrors.message = 'Message must be at least 10 characters';
}
return newErrors;
};
const handleChange = (e) => {
const { name, value } = e.target;
setFormData(prevState => ({
...prevState,
[name]: value
}));
if (errors[name]) {
setErrors(prevErrors => ({
...prevErrors,
[name]: ''
}));
}
};
const handleSubmit = async (e) => {
e.preventDefault();
const formErrors = validateForm();
if (Object.keys(formErrors).length > 0) {
setErrors(formErrors);
return;
}
setIsSubmitting(true);
setErrors({});
setSubmitStatus('');
try {
const response = await axios.post(`${API_BASE_URL}/api/contact`, formData, {
headers: {
'Content-Type': 'application/json'
},
timeout: 10000
});
if (response.data.success) {
setSubmitStatus('success');
setFormData({
name: '',
email: '',
subject: '',
message: ''
});
}
} catch (error) {
console.error('Error submitting form:', error);
if (error.code === 'ECONNABORTED') {
setSubmitStatus('timeout');
} else if (error.response?.status === 400) {
setSubmitStatus('validation');
} else {
setSubmitStatus('error');
}
} finally {
setIsSubmitting(false);
}
};
const getStatusMessage = () => {
switch (submitStatus) {
case 'success':
return {
type: 'success',
message: 'Thank you! Your message has been sent successfully. We\'ll get back to you soon!'
};
case 'timeout':
return {
type: 'error',
message: 'Request timed out. Please check your connection and try again.'
};
case 'validation':
return {
type: 'error',
message: 'Please check all fields and try again.'
};
default:
return {
type: 'error',
message: 'Sorry, there was an error sending your message. Please try again later.'
};
}
};
return (
<div className="contact-form-container">
<h2>Contact Us</h2>
{submitStatus && (
<div className={`status-message ${getStatusMessage().type}`}>
{getStatusMessage().message}
</div>
)}
<form onSubmit={handleSubmit} className="contact-form">
<div className="form-row">
<div className="form-group">
<label htmlFor="name">Name *</label>
<input
type="text"
id="name"
name="name"
value={formData.name}
onChange={handleChange}
required
placeholder="Your full name"
className={errors.name ? 'error' : ''}
disabled={isSubmitting}
/>
{errors.name && <span className="error-text">{errors.name}</span>}
</div>
<div className="form-group">
<label htmlFor="email">Email *</label>
<input
type="email"
id="email"
name="email"
value={formData.email}
onChange={handleChange}
required
placeholder="your.email@example.com"
className={errors.email ? 'error' : ''}
disabled={isSubmitting}
/>
{errors.email && <span className="error-text">{errors.email}</span>}
</div>
</div>
<div className="form-group">
<label htmlFor="subject">Subject *</label>
<input
type="text"
id="subject"
name="subject"
value={formData.subject}
onChange={handleChange}
required
placeholder="What's this about?"
className={errors.subject ? 'error' : ''}
disabled={isSubmitting}
/>
{errors.subject && <span className="error-text">{errors.subject}</span>}
</div>
<div className="form-group">
<label htmlFor="message">Message *</label>
<textarea
id="message"
name="message"
value={formData.message}
onChange={handleChange}
required
rows="6"
placeholder="Tell us more about your inquiry..."
className={errors.message ? 'error' : ''}
disabled={isSubmitting}
/>
{errors.message && <span className="error-text">{errors.message}</span>}
</div>
<button type="submit" className="submit-btn" disabled={isSubmitting}>
{isSubmitting ? (
<>
<span className="spinner"></span>
Sending...
</>
) : (
'Send Message'
)}
</button>
</form>
</div>
);
}
export default ContactForm;
In the snippet above, I updated the contact form component to include API requests to the backend server we set up. The API_BASE_URL variable is the backend Node server address. You'll need to update this address when you host your backend on a remote server.
The axios library is used to make the POST request to the backend server. The endpoint requires 4 parameters, which we pass through the formData variable.
Add these additional styles to ContactForm.css:
.status-message {
padding: 1rem;
border-radius: 4px;
margin-bottom: 1.5rem;
font-weight: 500;
}
.status-message.success {
background: #d4edda;
color: #155724;
border: 1px solid #c3e6cb;
}
.status-message.error {
background: #f8d7da;
color: #721c24;
border: 1px solid #f5c6cb;
}
.form-row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1rem;
}
@media (max-width: 768px) {
.form-row {
grid-template-columns: 1fr;
}
}
.spinner {
display: inline-block;
width: 16px;
height: 16px;
border: 2px solid transparent;
border-top: 2px solid currentColor;
border-radius: 50%;
animation: spin 1s linear infinite;
margin-right: 8px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.submit-btn:disabled {
background: #6c757d;
cursor: not-allowed;
opacity: 0.7;
}
Step 6: Testing Your Contact Form
Now let's test our contact form implementation:
Start the backend server: In your backend directory, run:
npm run dev
Start the React development server: Open another terminal window and navigate to your React project directory:
npm run dev
With both servers running, open your browser and go to localhost:5173. Fill out all required fields and submit the form.
You should see a success notification if everything is properly configured.
After that, check the email inbox you specified in the server.js file.
Troubleshooting Common Errors
When implementing a contact form with email notification in React, you'll likely encounter issues. Below, I've highlighted some common ones:
net::ERR_CONNECTION_REFUSED
This error indicates that the connection you're attempting to establish with the backend server was refused. It usually occurs if the backend server is unreachable or inactive.
To resolve this issue:
- Ensure the
API_BASE_URLis correct and corresponds to the server - Check that the backend server is running
- Verify the port number matches
Error: Invalid SenderAPIKey
This error indicates the API key you're using to authenticate your connection is invalid.
To fix this:
- Double-check that you've copied the correct API key
- Verify the environment variable is properly configured
- Ensure the
.envfile is in the correct directory
CORS Errors
If you encounter CORS errors, make sure you've properly configured the CORS middleware in your Express.js backend:
app.use(cors());
Best Practices and Security
Attackers often use contact forms to send malicious scripts. Here are some tips to help you build a battle-tested contact form.
1. Use Environment Variables
Never commit sensitive credentials to your repository. Always use environment variables:
// Good
const apiKey = process.env.SENDLAYER_API_KEY;
// Bad - Never do this
const apiKey = 'your-actual-api-key-here';
2. Sanitize Form Input
Always sanitize user inputs to prevent XSS attacks. Install security libraries:
npm install validator dompurify
Use them to sanitize form data:
const validator = require('validator');
// Sanitize inputs
const sanitizedName = validator.escape(name.trim());
const sanitizedSubject = validator.escape(subject.trim());
const sanitizedMessage = validator.escape(message.trim());
3. Implement Rate Limiting
Implement rate limiting to avoid abuse. Install express-rate-limit:
npm install express-rate-limit
Add it to your email endpoint:
const rateLimit = require('express-rate-limit');
const contactFormLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 5, // limit each IP to 5 requests per windowMs
message: 'Too many contact form submissions, please try again later.',
});
app.use('/api/contact', contactFormLimiter);
Frequently Asked Questions
These are answers to some of the top questions developers ask about building a contact form system in React.js.
How do I handle form validation in React.js?
Implement client-side validation using state and conditional rendering to display error messages. Use the useState hook to track validation errors and update them as users interact with form fields. Always validate on both the frontend and backend for security.
Can I create a contact form in React without a backend?
While React runs in the browser and cannot directly send emails, you can use third-party services like EmailJS, Formspree, or Netlify Forms to handle form submissions without building your own backend. However, having your own backend gives you more control and customization options.
How do I send form data to my email in React?
You'll need a backend service to send emails. React cannot send emails directly due to browser security restrictions. You can use Node.js with Nodemailer for SMTP, or integrate with email APIs like SendLayer for better deliverability and features.
How can I deploy my React contact form?
You'll need to deploy the frontend and backend services separately. Deploy the frontend to services like Vercel, Netlify, or GitHub Pages. The backend server can be deployed to platforms like Heroku, Railway, or DigitalOcean. Make sure to update your API URLs and configure environment variables for production.
Wrapping Up
In this comprehensive tutorial, we've built a complete contact form system with React.js and email notifications. Here are the key takeaways:
What We Built:
- A fully functional React contact form with validation
- Express.js backend server with SendLayer API integration
- Email notifications to site owners
- Auto-reply emails to users
- Proper error handling and user feedback
Have questions or want to share your implementation? Drop a comment below—I'd love to hear how you're implementing contact forms in your React projects!
Ready to implement this in your application? Start your free trial at SendLayer and send your first email in minutes.








Top comments (0)