DEV Community

Cover image for Creating a Fully Functional Contact Form with React and Formgrid API
Allen Jones
Allen Jones

Posted on

Creating a Fully Functional Contact Form with React and Formgrid API

A contact form is an essential component
of any website, allowing visitors to reach
out to you with their queries or feedback.

In this tutorial, I will show you how to create a contact form with Formgrid, React, and Styled Components.

We will start by creating the basic form structure and styling it with Styled Components.
Then we will integrate the Formgrid API to handle form submissions.

Formgrid is an open-source form backend and no-code form builder that makes it simple to handle form submissions without any server-side coding. By the end of this tutorial, you will have a working contact form that sends email notifications to your inbox whenever someone submits the form.


Table of Contents

  1. Prerequisites
  2. Setting Up the React Project
  3. Creating the Form Page Layout
  4. Creating The Details Bar Component
  5. Creating The Input Side and Input Fields
  6. Declaring The State Variables and Input Handler Functions
  7. Creating a Formgrid Account
  8. Handling Form Submission
  9. Creating The Success Page
  10. Redirecting Users to The Success Page
  11. Conclusion

Prerequisites

  • Basic knowledge of React and JavaScript
  • A free Formgrid account

Setting Up The React Project

First, create a new React project using create-react-app. 'Open your terminal, navigate to your desired directory, and
run the following command:

npx create-react-app formgrid-react-contact-form
Enter fullscreen mode Exit fullscreen mode

This will create a new React project named formgrid-react-contact-form.

Once the project is created, navigate to
the project root directory by running:

cd formgrid-react-contact-form
Enter fullscreen mode Exit fullscreen mode

We will be using styled-components for
styling our components and react-reveal
for fade-in animation.

Run the following command to install
the dependencies:

npm i styled-components react-reveal
Enter fullscreen mode Exit fullscreen mode

Creating The Form Page Layout

Now that we have our project set up let us create the page layout. In your project's src folder create a new folder called pages. Navigate to the pages folder and create a new file called
FormPage.js. Add the following code
to FormPage.js:

//FormPage.js
import React from 'react';
import styled from 'styled-components';

const PageWrapper = styled.div`
  display: flex;
  flex-direction: column;
  min-height: 100vh;
  align-items: center;
  background-color: whitesmoke;
  padding-bottom: 50px;
`;

const PageHeadingWrapper = styled.div`
  display: flex;
  flex-direction: column;
  margin-top: 40px;
`;

const FormContainer = styled.div`
  width: 70%;
  background-color: #fff;
  padding: 5px;
  border-radius: 5px;
  height: 70vh;
  @media (max-width: 768px) {
    width: 90%;
  }
`;

const TextOne = styled.b`
  font-size: 30px;
  color: rgb(4, 4, 59);
  text-align: center;
`;

const TextTwo = styled.p`
  color: rgb(4, 4, 34);
  font-size: 15px;
  text-align: center;
`;

const FormPage = () => {
  return (
    <PageWrapper>
      <PageHeadingWrapper>
        <TextOne>Contact US</TextOne>
        <TextTwo>
          Any Question or remarks? 
          Just write us a message
        </TextTwo>
      </PageHeadingWrapper>
      <FormContainer></FormContainer>
    </PageWrapper>
  );
};

export default FormPage;
Enter fullscreen mode Exit fullscreen mode


Creating The Details Bar Component

In your project's src folder create a
new folder called components.
Navigate to the components folder and create a new file called DetailsBar.js. Add the
following code to DetailsBar.js:

//DetailsBar.js
import React from 'react';
import styled from 'styled-components';
import * as Icon from 'react-feather';

const DetailsBarWrapper = styled.div`
  background-color: rgb(8, 8, 63);
  border-radius: 7px;
  position: relative;
  padding: 30px;
  display: flex;
  flex-direction: column;
  align-items: center;
  height: auto;
  padding-bottom: 100px;
  @media (max-width: 768px) {
    padding-bottom: 80px;
    grid-row: 2;
  }
`;

const TextWrapper = styled.div`
  display: flex;
  flex-direction: column;
  align-items: center;
`;

const TextOne = styled.p`
  text-align: center;
  color: #fff;
  font-size: 18px;
  font-weight: bold;
`;

const TextTwo = styled.p`
  text-align: center;
  color: #fff;
  font-size: 12px;
  line-height: 18px;
`;

const BigCircle = styled.div`
  height: 50px;
  margin-top: 30px;
  width: 50px;
  background-color: rgb(100, 21, 173);
  border-radius: 100%;
  z-index: 22;
  margin-left: 10px;
`;

const SmallCircle = styled.div`
  position: absolute;
  margin-left: 10px;
  background-color: rgb(252, 113, 137);
  border-radius: 100%;
  height: 30px;
  width: 30px;
`;

const ContactsWrapper = styled.a`
  display: flex;
  width: 200px;
  height: 10px;
  margin-top: 50px;
  cursor: pointer;
  text-decoration: none;
`;

const ContactText = styled.div`
  color: #fff;
  font-size: 15px;
  margin-left: 10px;
`;

const SocialsWrapper = styled.div`
  display: flex;
  justify-content: center;
  height: 10px;
  bottom: 30px;
  position: absolute;
  cursor: pointer;
`;

const SocialIconWrapper = styled.a`
  width: 35px;
  height: 35px;
  border-radius: 50%;
  display: flex;
  justify-content: center;
  align-items: center;
  &:hover {
    background-color: rgb(252, 113, 137);
  }
`;

const DetailsBar = () => {
  return (
    <DetailsBarWrapper>
      <TextWrapper>
        <TextOne>Contact Information</TextOne>
        <TextTwo>
          Fill up the form and our team 
          will get back to you within 
          24 hours
        </TextTwo>
      </TextWrapper>
      <div>
        <ContactsWrapper href="tel:+233543201893">
          <Icon.Phone 
            size={15} 
            color="rgb(252, 113, 137)" 
          />
          <ContactText>
            +233543201893
          </ContactText>
        </ContactsWrapper>

        <ContactsWrapper 
          href="mailto:aljay3334@gmail.com"
        >
          <Icon.Mail 
            size={15} 
            color="rgb(252, 113, 137)" 
          />
          <ContactText>
            aljay3334@gmail.com
          </ContactText>
        </ContactsWrapper>
      </div>

      <div>
        <BigCircle></BigCircle>
        <SmallCircle></SmallCircle>
      </div>

      <SocialsWrapper>
        <SocialIconWrapper 
          href="https://www.facebook.com"
        >
          <Icon.Facebook color="#fff" size={20} />
        </SocialIconWrapper>
        <SocialIconWrapper 
          href="https://www.instagram.com"
        >
          <Icon.Instagram color="#fff" size={20} />
        </SocialIconWrapper>
        <SocialIconWrapper 
          href="https://www.linkedin.com"
        >
          <Icon.Linkedin color="#fff" size={20} />
        </SocialIconWrapper>
      </SocialsWrapper>
    </DetailsBarWrapper>
  );
};

export default DetailsBar;
Enter fullscreen mode Exit fullscreen mode


In FormPage.js import the DetailsBar
component into it as shown in the
code below:

//FormPage.js
import React from 'react';
import styled from 'styled-components';
import DetailsBar from '../components/DetailsBar';

const FormPage = () => {
  return (
    <PageWrapper>
      <PageHeadingWrapper>
        <TextOne>Contact US</TextOne>
        <TextTwo>
          Any Question or remarks? 
          Just write us a message
        </TextTwo>
      </PageHeadingWrapper>
      <FormContainer>
        <DetailsBar />
      </FormContainer>
    </PageWrapper>
  );
};

export default FormPage;
Enter fullscreen mode Exit fullscreen mode

Creating The Input Side

Let us create the input side and add the input fields.
We will style the input fields with styled components and make them responsive using CSS media queries.

In the components folder create a
new file called InputSide.js and
add the following code:

//InputSide.js
import React from 'react';
import styled from 'styled-components';

const InputSideWrapper = styled.form`
  height: auto;
  padding-bottom: 100px;
  position: relative;
  padding: 10px 10px 100px 10px;
`;

const InputWrapper = styled.div`
  border: 2px solid transparent;
  width: 90%;
  padding-left: 10px;
  display: flex;
  flex-direction: column;
`;

const Input = styled.input`
  color: #333;
  width: 100%;
  font-size: 15px;
  padding: 8px;
  border-bottom: 1px solid rgb(100, 21, 173);
  border-left: 1px solid transparent;
  border-right: 1px solid transparent;
  border-top: 1px solid transparent;
  outline: 0px transparent !important;
`;

const MessageInput = styled.textarea`
  width: 100%;
  color: #333;
  font-size: 15px;
  padding: 10px;
  border-bottom: 1px solid rgb(100, 21, 173);
  border-left: 1px solid transparent;
  border-right: 1px solid transparent;
  border-top: 1px solid transparent;
  outline: 0px transparent !important;
`;

const SubMitButton = styled.input`
  position: absolute;
  bottom: 20px;
  right: 20px;
  padding: 10px;
  background-color: rgb(8, 8, 63);
  color: #fff;
  border: none;
  border-radius: 5px;
  padding: 12px 25px 12px 24px;
  cursor: pointer;
`;

const InputSide = () => {
  return (
    <InputSideWrapper>
      <InputWrapper>
        <p>Name</p>
        <Input 
          type="text" 
          placeholder="Allen Jones" 
        />
      </InputWrapper>
      <InputWrapper>
        <p>Email</p>
        <Input 
          type="email" 
          placeholder="aljay126@gmail.com" 
        />
      </InputWrapper>
      <InputWrapper>
        <p>Phone</p>
        <Input 
          type="number" 
          placeholder="+233546227893" 
        />
      </InputWrapper>
      <InputWrapper>
        <p>Message</p>
        <MessageInput 
          placeholder="Write your message" 
        />
      </InputWrapper>
      <SubMitButton 
        type="submit" 
        value="Send Message" 
      />
    </InputSideWrapper>
  );
};

export default InputSide;
Enter fullscreen mode Exit fullscreen mode

In FormPage.js import the InputSide
component into it:

//FormPage.js
import React from 'react';
import styled from 'styled-components';
import DetailsBar from '../components/DetailsBar';
import InputSide from '../components/InputSide';

const FormPage = () => {
  return (
    <PageWrapper>
      <PageHeadingWrapper>
        <TextOne>Contact US</TextOne>
        <TextTwo>
          Any Question or remarks? 
          Just write us a message
        </TextTwo>
      </PageHeadingWrapper>
      <FormContainer>
        <DetailsBar />
        <InputSide />
      </FormContainer>
    </PageWrapper>
  );
};

export default FormPage;
Enter fullscreen mode Exit fullscreen mode


Declaring The State Variables and

Input Handler Functions

Let us declare the state variables and add the functions for handling
the form inputs:

//InputSide.js
const InputSide = () => {
  const [name, setName] = React.useState('');
  const [email, setEmail] = React.useState('');
  const [phone, setPhone] = React.useState('');
  const [message, setMessage] = 
    React.useState('');
  const [buttonLoading, setButtonLoading] = 
    React.useState(false);

  const nameHandler = (e) => {
    setName(e.target.value);
  };

  const emailHandler = (e) => {
    setEmail(e.target.value);
  };

  const phoneHandler = (e) => {
    setPhone(e.target.value);
  };

  const messageHandler = (e) => {
    setMessage(e.target.value);
  };
};

export default InputSide;
Enter fullscreen mode Exit fullscreen mode

In this code, we have used the useState hook to create state variables for the form inputs and the loading state of the submit button.
We have also added the input handler functions.


Creating a Formgrid Account

Go to formgrid.dev
and sign up using Google or Email.
No credit card required.




Once you are logged in click New Form from your dashboard and give your form a name. Click Create Form.

You will be taken to your form's
Overview tab. Here you will find your
unique Endpoint URL. Copy it.
It looks like this:

https://formgrid.dev/api/f/your-form-id
Enter fullscreen mode Exit fullscreen mode

This is the URL you will use to send your form submissions.
Every time someone submits your form, the data is sent to this endpoint, and Formgrid emails you the submission details instantly.

Unlike Formspree, you do not need to
configure anything else. Email notifications are enabled by default
using the email address you signed up with.


Handling Form Submission

Now let us add the function for handling form submission.
Replace the contents
of InputSide.js with the following:

//InputSide.js
import React from 'react';
import styled from 'styled-components';

const InputSide = () => {
  const [name, setName] = React.useState('');
  const [email, setEmail] = React.useState('');
  const [phone, setPhone] = React.useState('');
  const [message, setMessage] = 
    React.useState('');
  const [buttonLoading, setButtonLoading] = 
    React.useState(false);

  const nameHandler = (e) => {
    setName(e.target.value);
  };

  const emailHandler = (e) => {
    setEmail(e.target.value);
  };

  const phoneHandler = (e) => {
    setPhone(e.target.value);
  };

  const messageHandler = (e) => {
    setMessage(e.target.value);
  };

  const handleSubmit = async (e) => {
    e.preventDefault();
    setButtonLoading(true);

    const response = await fetch(
      'https://formgrid.dev/api/f/your-form-id',
      {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({ 
          name, 
          email, 
          phone, 
          message 
        }),
      }
    );

    if (response.ok) {
      setButtonLoading(false);
      alert('Message sent successfully!');
    } else {
      setButtonLoading(false);
      alert('Failed to send message');
    }
  };

  return (
    <InputSideWrapper onSubmit={handleSubmit}>
      <InputWrapper>
        <p>Name</p>
        <Input
          type="text"
          placeholder="Allen Jones"
          value={name}
          onChange={nameHandler}
          required
        />
      </InputWrapper>
      <InputWrapper>
        <p>Email</p>
        <Input
          type="email"
          placeholder="aljay126@gmail.com"
          value={email}
          onChange={emailHandler}
          required
        />
      </InputWrapper>
      <InputWrapper>
        <p>Phone</p>
        <Input
          type="number"
          placeholder="+233546227893"
          value={phone}
          onChange={phoneHandler}
        />
      </InputWrapper>
      <InputWrapper>
        <p>Message</p>
        <MessageInput
          placeholder="Write your message"
          value={message}
          onChange={messageHandler}
          required
        />
      </InputWrapper>
      <SubMitButton
        type="submit"
        value={buttonLoading 
          ? 'Sending...' 
          : 'Send Message'}
        disabled={buttonLoading}
      />
    </InputSideWrapper>
  );
};

export default InputSide;
Enter fullscreen mode Exit fullscreen mode

Replace your-form-id with the actual endpoint slug from your Formgrid form overview tab.

In this code, we use the fetch API to make a POST request to your Formgrid endpoint with the form data as JSON.
Formgrid receives the submission, saves it to your dashboard, and sends you an email notification instantly.

If the response is successful, the
if block handles it. Otherwise
the else block handles errors.

What happens after submission

When someone submits your form you will receive an email notification that looks like this:

New Submission: Contact Form

Name: Allen Jones
Email: aljay126@gmail.com
Phone: +233546227893
Message: Hello, I would like to 
         get in touch.

Submitted: 10:30 AM, March 22, 2026
Enter fullscreen mode Exit fullscreen mode

Clean. Organized. Ready to act on.

You can also view all submissions in your Formgrid dashboard at any time.
No submission is ever lost, even if the email notification fails to arrive.


Adding spam protection

Formgrid includes built-in spam protection controls in the Security Settings section for each form (available on all plans).

You can protect your form in two ways:

  1. Add a honeypot field to your frontend form, and
  2. Enable protections in Form Details → Settings → Security Settings.

If you are adding a honeypot field manually, use this field name:

<input 
  type="text" 
  name="honeypot" 
  style={{ display: 'none' }} 
  tabIndex={-1}
  autoComplete="off"
  aria-hidden="true"
/>
Enter fullscreen mode Exit fullscreen mode

OR
You can click on the form to visit the:
Form Details page. This is where you can manage everything related to that specific form.



Step 3: Navigate to the form Settings Tab

At the top of the Form Details page, you will see a row of tabs. Click on the Settings tab to open the form's configuration options.



Step 4: Scroll Down to the Security Settings Section

Within the Settings tab, scroll down until you reach the Security Settings section. This is where all of Formgrid's spam and abuse protection tools are located.



You will see the following options:

Enable CAPTCHA:

Adds a Google reCAPTCHA "I'm not a robot" verification step to your form. When enabled, anyone submitting your form will need to complete the CAPTCHA challenge before their submission goes through. Real human users can do this easily. Automated bots typically cannot, which stops the majority of spam submissions before they ever reach your inbox.

Enable Honeypot:

Adds a hidden field to your form that is completely invisible to real users but visible to bots. When a bot fills in the form, it fills in every field it can detect, including the hidden honeypot field. Formgrid detects this and automatically discards the submission as spam. Legitimate users never see or interact with this field, so it has no impact on the experience of real visitors filling in your form.

Allowed Domains (CORS):

Allows you to specify which domains are permitted to submit data to your form. If your form is embedded on a specific website, you can enter that domain here. Any submission coming from a different domain will be rejected. This is particularly effective against bots that submit directly to your form endpoint without visiting your website at all.

Rate Limiting:

Sets a maximum number of submissions allowed per IP address per minute. This prevents any single source from flooding your form with repeated submissions in a short period. If you expect normal users to submit your form occasionally, a limit of 3 to 5 submissions per minute per IP is a reasonable starting point.

Bots that automatically fill every field will fill in this hidden field. Formgrid detects this and automatically rejects the submission.


Creating The Success Page

The success page is where users will
be directed after form submission.
In src/pages/ create a new file
called SuccessPage.js and add
the following code:

//SuccessPage.js
import React from 'react';
import * as Icon from 'react-feather';
import { Fade } from 'react-reveal';
import styled from 'styled-components';

const MessageWrapper = styled.div`
  margin-top: 150px;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
`;

const SuccessMessage = styled.h2`
  font-size: 25px;
  color: rgb(8, 8, 63);
  @media (max-width: 768px) {
    font-size: 18px;
  }
`;

const SuccessPage = () => {
  return (
    <React.Fragment>
      <Fade bottom duration={700} distance="60px">
        <MessageWrapper>
          <Icon.CheckCircle 
            color="rgb(8, 8, 63)" 
            style={{ width: 50, height: 50 }} 
          />
          <SuccessMessage>
            MESSAGE SENT SUCCESSFULLY
          </SuccessMessage>
        </MessageWrapper>
      </Fade>
    </React.Fragment>
  );
};

export default SuccessPage;
Enter fullscreen mode Exit fullscreen mode

Redirecting Users to The Success Page

We will use react-router-dom to handle
navigation between the form page and
the success page.

Run the following command to install it:

npm install react-router-dom
Enter fullscreen mode Exit fullscreen mode

Open App.js and replace its contents
with the following:

//App.js
import React from 'react';
import { 
  BrowserRouter as Router, 
  Routes, 
  Route 
} from 'react-router-dom';
import FormPage from './pages/FormPage';
import SuccessPage from './pages/SuccessPage';

const App = () => {
  return (
    <Router>
      <div>
        <Routes>
          <Route path="/" element={<FormPage />} />
          <Route 
            path="/success" 
            element={<SuccessPage />} 
          />
        </Routes>
      </div>
    </Router>
  );
};

export default App;
Enter fullscreen mode Exit fullscreen mode

Now update InputSide.js to redirect
the user to the success page after
a successful submission:

//InputSide.js
import React from 'react';
import { useNavigate } from 'react-router-dom';

const InputSide = () => {

  const navigate = useNavigate();

  const handleSubmit = async (e) => {
    e.preventDefault();
    setButtonLoading(true);

    const response = await fetch(
      'https://formgrid.dev/api/f/your-form-id',
      {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({ 
          name, 
          email, 
          phone, 
          message 
        }),
      }
    );

    if (response.ok) {
      setButtonLoading(false);
      navigate('/success');
    } else {
      setButtonLoading(false);
      alert('Failed to send message');
    }
  };

};

export default InputSide;
Enter fullscreen mode Exit fullscreen mode

Why Use Formgrid Instead of Formspree

Both Formgrid and Formspree handle form
submissions reliably. Here is how
they compare:

Feature Formspree Formgrid
Free submissions 50/month 50/month
Form builder
Self-hostable
Open source
Google Sheets native $90/month $16/month
Custom email templates $90/month $29/month
Submission dashboard
Spam protection

Formgrid gives you everything Formspree
offers on the free plan, plus a no-code
form builder, self-hosting via Docker,
and significantly cheaper paid plans.


Conclusion

In this tutorial, we learnt how to create
a contact form in React using the Formgrid
API and Styled Components. We also added
react-router-dom for navigation between
the form page and the success page.

With Formgrid, your form submissions are
handled reliably, saved to a dashboard,
and emailed to you instantly. No
server-side code. No backend setup.
Just a simple endpoint URL.

Demo
Source Code

👉 Try Formgrid free at formgrid.dev


Tags: #react #webdev #javascript

tutorial #formgrid


markdown

---
Enter fullscreen mode Exit fullscreen mode

Top comments (0)