DEV Community πŸ‘©β€πŸ’»πŸ‘¨β€πŸ’»

Cover image for How to build a Contact form with Formik in Next JS and TypeScript
Remi W.
Remi W.

Posted on • Originally published at creativedesignsguru.com

How to build a Contact form with Formik in Next JS and TypeScript

In this article, we'll learn how to build a form using Next, TypeScript, and Formik. We'll be building a simple contact form with basic validation before submitting it. Formik is flexible library for building forms in React and React Native.

Set up project

Let's create the project for this tutorial. Open your terminal and enter the following command.

npx create-next-app@latest --ts nextjs-formik-demo
Enter fullscreen mode Exit fullscreen mode

This will create a next project based on TypeScript. Here, I've named the project nextjs-formik-demo.
Once the project initialization is done, go to the project directory and run the development server.

cd nextjs-formik-demo
npm run dev
Enter fullscreen mode Exit fullscreen mode

Your server will normally be running on http://localhost:3000.

Next TypeScript index development page

Great, let's now modify the index.tsx file and create the form.

Creating the form

Before going further, let's install the bootstrap UI package. It'll be very useful to quickly create a form. We'll also install formik and yup.

npm install bootstrap formik yup
Enter fullscreen mode Exit fullscreen mode

Once it's done go to index.tsx file and let's start modifying it.
First of all, let's import the packages we'll be using.

import { useState } from 'react';

import { useFormik } from 'formik';
import * as yup from 'yup';

import 'bootstrap/dist/css/bootstrap.min.css';
...
Enter fullscreen mode Exit fullscreen mode
  • useState: a hook that allows you to have state variables in functional components
  • Formik: a React package that helps in forms creation, validation, and submission.
  • Yup: a JavaScript schema builder for value parsing and validation
  • bootstrap: we are directly importing the CSS files so we can use bootstrap CSS classes to style our components.

Next step, let's create values and object we'll be using for the next steps.

...
import type { NextPage } from 'next';

const Home: NextPage = () => {
  const [message, setMessage] = useState(''); // This will be used to show a message if the submission is successful
  const [submitted, setSubmitted] = useState(false);

  const formik = useFormik({
    initialValues: {
      email: '',
      name: '',
      message: '',
    },
    onSubmit: () => {
      setMessage('Form submitted');
      setSubmitted(true);
    },
    validationSchema: yup.object({
      name: yup.string().trim().required('Name is required'),
      email: yup
        .string()
        .email('Must be a valid email')
        .required('Email is required'),
      message: yup.string().trim().required('Message is required'),
    }),
  });
...
Enter fullscreen mode Exit fullscreen mode

What are we doing here?

  • message & submitted: This will help show a message that will be displayed when the form is successfully submitted
  • formik: we use the useFormik hooks to create a Formik object. It contains the initial values, the onSubmit method followed by a validation schema validationSchema built with Yup.

It's basically all we need for a form in just a few lines. Let's move to the UI and start using the formik object.

...
<div className="vh-100 d-flex flex-column justify-content-center align-items-center">
  <div hidden={!submitted} className="alert alert-primary" role="alert">
    {message}
  </div>

  <form className="w-50" onSubmit={formik.handleSubmit}>
    {/* Adding the inputs */}
  </form>
</div>
...
Enter fullscreen mode Exit fullscreen mode

We want to display an alert every time the form is successfully submitted. That's what this piece of code achieves:

<div hidden={!submitted} className="alert alert-primary" role="alert">
  {message}
</div>
Enter fullscreen mode Exit fullscreen mode

We can now add the inputs. For each input, we'll be adding the label, the input and the error message for each field.
Let's start with the name field.

<form className="w-50" onSubmit={formik.handleSubmit}>
  <div className="mb-3">
    <label htmlFor="name" className="form-label">
      Name
    </label>
    <input
      type="text"
      name="name"
      className="form-control"
      placeholder="John Doe"
      value={formik.values.name}
      onChange={formik.handleChange}
      onBlur={formik.handleBlur}
    />
    {formik.errors.name && (
      <div className="text-danger">{formik.errors.name}</div>
    )}
  </div>
  ...
</form>
Enter fullscreen mode Exit fullscreen mode

And then the email field.

<form className="w-50" onSubmit={formik.handleSubmit}>
  ...
  <div className="mb-3">
    <label htmlFor="email" className="form-label">
      Email
    </label>
    <input
      type="email"
      name="email"
      className="form-control"
      placeholder="john@example.com"
      value={formik.values.email}
      onChange={formik.handleChange}
      onBlur={formik.handleBlur}
    />
    {formik.errors.email && (
      <div className="text-danger">{formik.errors.email}</div>
    )}
  </div>
  ...
</form>
Enter fullscreen mode Exit fullscreen mode

And next, the message field.

<form className="w-50" onSubmit={formik.handleSubmit}>
  ...
  <div className="mb-3">
    <label htmlFor="message" className="form-label">
      Message
    </label>
    <textarea
      name="message"
      className="form-control"
      placeholder="Your message ..."
      value={formik.values.message}
      onChange={formik.handleChange}
      onBlur={formik.handleBlur}
    />
    {formik.errors.message && (
      <div className="text-danger">{formik.errors.message}</div>
    )}
  </div>
  ...
</form>
Enter fullscreen mode Exit fullscreen mode

And finally the submit button.

<form className="w-50" onSubmit={formik.handleSubmit}>
  ...
  <button type="submit" className="btn btn-primary">
    Send
  </button>
</form>
Enter fullscreen mode Exit fullscreen mode

And here's the final code of the form.

<div className="vh-100 d-flex flex-column justify-content-center align-items-center">
  <div hidden={!submitted} className="alert alert-primary" role="alert">
    {message}
  </div>

  <form className="w-50" onSubmit={formik.handleSubmit}>
    <div className="mb-3">
      <label htmlFor="name" className="form-label">
        Name
      </label>
      <input
        type="text"
        name="name"
        className="form-control"
        placeholder="John Doe"
        value={formik.values.name}
        onChange={formik.handleChange}
        onBlur={formik.handleBlur}
      />
      {formik.errors.name && (
        <div className="text-danger">{formik.errors.name}</div>
      )}
    </div>

    <div className="mb-3">
      <label htmlFor="email" className="form-label">
        Email
      </label>
      <input
        type="email"
        name="email"
        className="form-control"
        placeholder="john@example.com"
        value={formik.values.email}
        onChange={formik.handleChange}
        onBlur={formik.handleBlur}
      />
      {formik.errors.email && (
        <div className="text-danger">{formik.errors.email}</div>
      )}
    </div>

    <div className="mb-3">
      <label htmlFor="message" className="form-label">
        Message
      </label>
      <textarea
        name="message"
        className="form-control"
        placeholder="Your message ..."
        value={formik.values.message}
        onChange={formik.handleChange}
        onBlur={formik.handleBlur}
      />
      {formik.errors.message && (
        <div className="text-danger">{formik.errors.message}</div>
      )}
    </div>

    <button type="submit" className="btn btn-primary">
      Send
    </button>
  </form>
</div>
Enter fullscreen mode Exit fullscreen mode

And the form is operational now. If you noticed, we are conditionally showing errors in the forms using formik.errors.

{formik.errors.name && (
  <div className="text-danger">{formik.errors.name}</div>
)}
Enter fullscreen mode Exit fullscreen mode

This will display an error under the name field for example.

Next JS Formik Error message

Here's the final code for index.tsx.

import { useState } from 'react';

import { useFormik } from 'formik';
import type { NextPage } from 'next';
import * as yup from 'yup';

import 'bootstrap/dist/css/bootstrap.min.css';

const Home: NextPage = () => {
  const [message, setMessage] = useState(''); // This will be used to show a message if the submission is successful
  const [submitted, setSubmitted] = useState(false);

  const formik = useFormik({
    initialValues: {
      email: '',
      name: '',
      message: '',
    },
    onSubmit: () => {
      setMessage('Form submitted');
      setSubmitted(true);
    },
    validationSchema: yup.object({
      name: yup.string().trim().required('Name is required'),
      email: yup
        .string()
        .email('Must be a valid email')
        .required('Email is required'),
      message: yup.string().trim().required('Message is required'),
    }),
  });

  return (
    <div className="vh-100 d-flex flex-column justify-content-center align-items-center">
      <div hidden={!submitted} className="alert alert-primary" role="alert">
        {message}
      </div>

      <form className="w-50" onSubmit={formik.handleSubmit}>
        <div className="mb-3">
          <label htmlFor="name" className="form-label">
            Name
          </label>
          <input
            type="text"
            name="name"
            className="form-control"
            placeholder="John Doe"
            value={formik.values.name}
            onChange={formik.handleChange}
            onBlur={formik.handleBlur}
          />
          {formik.errors.name && (
            <div className="text-danger">{formik.errors.name}</div>
          )}
        </div>

        <div className="mb-3">
          <label htmlFor="email" className="form-label">
            Email
          </label>
          <input
            type="email"
            name="email"
            className="form-control"
            placeholder="john@example.com"
            value={formik.values.email}
            onChange={formik.handleChange}
            onBlur={formik.handleBlur}
          />
          {formik.errors.email && (
            <div className="text-danger">{formik.errors.email}</div>
          )}
        </div>

        <div className="mb-3">
          <label htmlFor="message" className="form-label">
            Message
          </label>
          <textarea
            name="message"
            className="form-control"
            placeholder="Your message ..."
            value={formik.values.message}
            onChange={formik.handleChange}
            onBlur={formik.handleBlur}
          />
          {formik.errors.message && (
            <div className="text-danger">{formik.errors.message}</div>
          )}
        </div>

        <button type="submit" className="btn btn-primary">
          Send
        </button>
      </form>
    </div>
  );
};

export default Home;
Enter fullscreen mode Exit fullscreen mode

And voilΓ . We've just integrated Formik to a Next project in Typescript with Boostrap and Yup.
Here's a GIF showing the demo.

Next JS Form with Formik

Conclusion

In this article, we've learned how to build a contact form using Formik and Yup with Next and TypeScript.

React SaaS Boilerplate

React SaaS Boilerplate is the perfect starter kit to launch your SaaS faster and better. Focus on your business, products and customers instead of losing your time to implement basic functionalities like authentication, recurring payment, landing page, user dashboard, form handling, error handling, CRUD operation, database, etc.

Next JS SaaS Boilerplate Starter

Top comments (5)

Collapse
 
lukeshiru profile image
Luke Shiru

2 things:

First: Is worth mentioning that you don't need Formik for simple forms such as this one, you can keep it really simple with a few states and native validations.

Example of how that would look like in this spoiler
import "bootstrap/dist/css/bootstrap.min.css";
import type { NextPage } from "next";
import { useEffect, useRef, useState } from "react";

const Home: NextPage = () => {
    const textareaRef = useRef<HTMLTextAreaElement>();
    const [email, setEmail] = useState("");
    const [message, setMessage] = useState("");
    const [name, setName] = useState("");
    const [submitMessage, setSubmitMessage] = useState("");

    // We need this to have native validation in the textarea
    // this is because textarea doesn't have a pattern attribute
    useEffect(
        () =>
            textareaRef.current?.setCustomValidity(
                message.trim() === "" ? "Message is required" : "",
            ),
        [message],
    );

    return (
        <div className="vh-100 d-flex flex-column justify-content-center align-items-center">
            <div
                hidden={submitMessage === ""}
                className="alert alert-primary"
                role="alert"
            >
                {submitMessage}
            </div>

            <form
                className="w-50"
                onSubmit={() => setSubmitMessage("Form submitted")}
            >
                <div className="mb-3">
                    <label htmlFor="name" className="form-label">
                        Name
                    </label>
                    <input
                        className="form-control"
                        name="name"
                        onChange={event => setName(event.currentTarget.value)}
                        pattern=".*\S+.*"
                        placeholder="John Doe"
                        required
                        type="text"
                        value={name}
                    />
                </div>

                <div className="mb-3">
                    <label htmlFor="email" className="form-label">
                        Email
                    </label>
                    <input
                        className="form-control"
                        name="email"
                        onChange={event => setEmail(event.currentTarget.value)}
                        placeholder="john@example.com"
                        required
                        type="email"
                        value={email}
                    />
                </div>

                <div className="mb-3">
                    <label htmlFor="message" className="form-label">
                        Message
                    </label>
                    <textarea
                        className="form-control"
                        name="message"
                        placeholder="Your message ..."
                        ref={textareaRef}
                        required
                        value={message}
                        onChange={event =>
                            setMessage(event.currentTarget.value)
                        }
                    />
                </div>

                <button type="submit" className="btn btn-primary">
                    Send
                </button>
            </form>
        </div>
    );
};

export default Home;
Enter fullscreen mode Exit fullscreen mode

The good thing about this approach is that you don't introduce new dependencies and you're using what comes with React and the browser. If you use tools such as Remix then this becomes even simpler, by not needing useState, or the useEffect and useRef for the textarea.

Second: The Content Policy of DEV states the following:

If a post contains affiliate links, that fact must be clearly disclosed. For instance, with language such as: β€œThis post includes affiliate links; I may receive compensation if you purchase products or services from the different links provided in this article.”

And that "React SaaS Boilerplate" thing at the bottom is like and ad at this point, so ideally you should clarify that the post contains ads.

Cheers!

Collapse
 
suhakim profile image
sadiul hakim

That is greatπŸ‘

Collapse
 
ixartz profile image
Remi W.

Thank you!

Collapse
 
abdelrahmanlotfy profile image
AbdElRahmanLotfy

its not with typescript

Collapse
 
ixartz profile image
Remi W.

This project is generated with --ts in npx create-next-app@latest --ts.

All files is using .tsx and .ts. All files are compatible with TypeScript.

Git push

Stop by this week's meme thread!