Build Next JS 14 Form Using Formik | 2024

How to create and build a scalable Form for your Next JS 14 project with FormIk is discussed in detail.

Pritam Banerjee
7 min readApr 3, 2024
Next JS 14 with FormIk to build scalable Next JS 14

Introduction

Building a scalable full-fledged form in Next JS 14 is important nowadays. As we are working with the Next JS 14 app router we know that we can create a form in the Server Component but in most cases, we need to use the Client Component to implement the Form. There are a couple of Form Libraries are there form example react-hook-form, formik etc. Most of the form will work with Next JS 14 but FormIk will work with Next JS 14 out of the box. So in this tutorial, we will discuss how to create a scalable form with proper validation using Formik and Typescript.

Installation

You can do the installation by using any one of the ways that you would like. You can choose npm or yarn.

npm install formik --save
or
yarn add formik

We will use the NextUI library to create the UI. Next UI is a great UI library for creating UI components and also it has out-of-the-box features and customisation options which will be heavily compatible with Next JS 14.

npm i @nextui-org/react framer-motion
or
yarn add @nextui-org/react framer-motion
or
pnpm add @nextui-org/react framer-motion
or
bun add @nextui-org/react framer-motion

For validating form we need to some validator and here zod will solve and make our life will easy. Zod is a powerful library by which we can do any type of simple to very complex validation very easily and I will recommend using zod library for your Form validation instead of yup.

Login Form

So we will create a login form using Formik, we already installed the Formik, So now we will create a Login component in the app/components directory and this Login component will be the client component. We will create a route namely “/login” that we can create by creating a login folder in the app directory and then defining the page.tsx and layout.tsx file and we will create a route. The implementation has been given below.

app/login/layout.tsx

export default function LoginLayout({
children,
}: {
children: React.ReactNode
}) {
return (
{children}
)
}

app/login/page.tsx

import { Suspense } from "react";
import {Skeleton} from "@nextui-org/react";
import { Metadata } from "next";
import Login from "@app/components/Login"


export async function generateMetadata({
params,
}: {
params: { slug: string };
}): Promise<Metadata> {
return {
title: "Login Form",
description: "Login Form Meta Description Should be here",
};
}

export default async function LoginPage({
params,
}: {
params: { slug: string };
}) {
return (
<>
<Suspense fallback={<Skeleton />}>
<Login/>
</Suspense>
</>
);
}

Once you have created this structure then you have created the login route with the Login component and with the SEO title and meta title added to the login route. Till now we just imported the Login component but we did not created will create that in next phase.

Create Schema of Login Form Using Zod

Now create a folder in the app directory called utils, In this utils folder create another folder called Schemas. We are creating the folder structure based on the Next JS 14 official documentation way but you can create the folder structure based on your way whichever you would like. In the Schemas folder create a file called LoginSchema.ts and then write this schema code given below.

app/utils/Schemas/LoginSchema.ts

import { z } from "zod";
const requiredMessage: string = "This field is required";

export const LoginSchema = z.object({
username: z.string({
required_error: requiredMessage,
}),
password: z.string({
required_error: requiredMessage,
}),
});

As you can see we have used zod function to create the schema and cool thing about zod is that you can define any schema here and it will automatically work in the component and you do not need to write extra logic in your code for the validation using zod and this will make code maintainability easy, make it more reusable.

Create Login Client Component

Now it’s time to create a client component namely Login. Client Component means the component which works on the Client side not on the server side and we can access browser APIs, browser events, and hooks in this client component. By default, all Next JS 14 app folder components are server components and if you want to make those components as client components then you need to use “use client” directive.

app/components/Login.tsx

import { useRef } from "react";
import { TypeOf } from "zod";
import { ErrorMessage, Field, Form, Formik } from "formik";
import {Button } from "@nextui-org/react";
import { toFormikValidationSchema } from "zod-formik-adapter";

function Error({ msg }: { msg: string }) {
return <p className="text-red-500 absolute">{msg}</p>;
}

type LoginModel = TypeOf<typeof LoginSchema>;

export const Login = () => {
const initialValues: LoginModel = {
username: "",
password: "",
};
const loginFormRef = useRef<any>(null);

const submit = (formData: LoginModel) => {
console.log('form values', formData);
}

return (
<div className="singin-form">
<Formik<LoginModel>
initialValues={initialValues}
validationSchema={toFormikValidationSchema((LoginSchema as any))}
onSubmit={(values: any, actions) => {submit(values);}}>
{({ values, errors, touched, isSubmitting, isValidating, status, submitCount }) => (
<Form className="w-full" ref={loginFormRef}>
<>
<div className="m-auto">
<article className="login-form">
<div className="flex flex-wrap -mx-3 mb-6">
<div className="w-full mb-6">
<div className="md:w-1/2 px-3">
<label className="block uppercase tracking-wide text-gray-700 text-[11px] font-bold mb-2 required-label">
Username
</label>
<Field
type="text"
name="username"
placeholder="Username"
className={
"appearance-none block w-full bg-gray-200 text-gray-700 border rounded py-3 px-4 mb-3 leading-tight focus:outline-none focus:bg-white" +
" " +
(errors.username && touched.username
? " border-red-500"
: "")
}
/>
<ErrorMessage
className="text-red-500 text-[11px] italic"
name="username"
render={(msg) => <Error msg={msg} />}
/>
</div>
</div>
<div className="w-full mb-6">
<div className="md:w-1/2 px-3">
<label className="block uppercase tracking-wide text-gray-700 text-[11px] font-bold mb-2 required-label">
Password
</label>
<Field
type="password"
name="password"
placeholder="Password"
className={
"appearance-none block w-full bg-gray-200 text-gray-700 border rounded py-3 px-4 mb-3 leading-tight focus:outline-none focus:bg-white" +
" " +
(errors.password && touched.password
? " border-red-500"
: "")
}
/>
<ErrorMessage
className="text-red-500 text-[11px] italic"
name="password"
render={(msg) => <Error msg={msg} />}
/>
</div>
</div>
<div className="w-full md:w-1/3 px-3 mb-6 md:mb-0 pt-3">
<Button
className={"uppercase font-medium text-white"}
color="primary"
type={"submit"}
disabled={
Array.isArray(errors) ||
Object.values(errors).toString() != ""
}
>
Signin
</Button>
</div>
</div>

</article>
</div>
</>
</Form>
)}
</Formik>
</div>
)

}

So this is the entire code for Login Form with proper and basic validation. This is pretty straight forward and if you understand then it will be easy to reuse this logic and create other forms using Formik in Next JS 14 structure.

Ok so now let’s understand the code step by step. Here we have created a TypeScript model from the zod schema by using this code.

type LoginModel = TypeOf<typeof LoginSchema>;

Once we have the schema it will contain username and password key-value pair because that we have defined in the zod schema and this line extracts the schema from zod and transform it to TypeScript model. Once we have the schema now create the intialvalues of the form and that is mandatory in Formik component.

const initialValues: LoginModel = {
username: "",
password: "",
};

So once the initial values have been created, now let’s create the Formik component and you can see we have put a couple of configurations in the Formik props and you just need to remember it or reuse the code in other forms to make it work.

<Formik<LoginModel>
initialValues={initialValues}
validationSchema={toFormikValidationSchema((LoginSchema as any))}
onSubmit={(values: any, actions) => {submit(values);}}>

Once we have the FormIk component written then write the field inside the children of FormIk component. For form fields, we will use the Field component from FormIk which will act as a controller component of the regular form field. We can define the input fields by using this way given below.

<Field
type="text"
name="username"
placeholder="Username"
className={
"appearance-none block w-full bg-gray-200 text-gray-700 border rounded py-3 px-4 mb-3 leading-tight focus:outline-none focus:bg-white" +
" " +
(errors.username && touched.username ? " border-red-500": "")
}
/>

Once we have defined the input fields by using the Field component now we need to define the Error component because if any validation error occurs then we need to use this Error component to show the error in the UI.

 <ErrorMessage
className="text-red-500 text-[11px] italic"
name="username"
render={(msg) => <Error msg={msg} />}
/>

So now we add the button and we successfully create the LoginForm using Next JS 14 with FormIk and by using this code it will be very easy to create Login Form for Next JS 14.

Conclusion

So we have created a full-fledged login form with validation using zod and by using FormIk with Next Js 14 the Form implementation is simple and also it will be very easy to extend the form easily and scalable as well to create complex and nested forms. So if you are building Next Js 14 project then use this approach to create a Form in your Next Js 14 application.

--

--

Pritam Banerjee
Pritam Banerjee

Written by Pritam Banerjee

I am Full Stack Developer and Data Scientist, I have worked some of the biggest client’s project (eg. Walmart, Cisco, Uber, Apple, JP Morgan, Capital One etc).

No responses yet