DEV Community

Cover image for Next JS | Multi-Step Form
Shubham Tiwari
Shubham Tiwari

Posted on • Updated on

Next JS | Multi-Step Form

Hello Everyone, today i will show you how you can create a multistep form in next js using a simple method.

Technologies Used - NEXT JS with TailwindCSS

Let's get started...

Creating 3 different form components

Username and Email

// UserNameEmail.js
import React from 'react'

const UserNameEmail = (props) => {
    const {data,handleChange} = props

    return (
        <div className="max-w-xs md:max-w-lg mx-auto">
            <form className="bg-white shadow-md rounded px-8 pt-6 pb-8 mb-4">
                <div className="mb-10">
                    <label className="block text-gray-700 text-sm font-bold mb-2" htmlFor="name">
                        Username
                    </label>
                    <input className="appearance-none block w-full bg-gray-200 text-gray-700 border border-gray-200 rounded py-3 px-4 leading-tight focus:outline-none focus:bg-white focus:border-gray-500" name="name" type="text" placeholder="Username..." value={data.name} onChange={handleChange} />
                </div>
                <div>
                    <label className="block text-gray-700 text-sm font-bold mb-2" htmlFor="email">
                        Email
                    </label>
                    <input className="appearance-none block w-full bg-gray-200 text-gray-700 border border-gray-200 rounded py-3 px-4 leading-tight focus:outline-none focus:bg-white focus:border-gray-500" name="email" type="email" placeholder="Email..." value={data.email} onChange={handleChange} />
                </div>
            </form>
        </div>
    )
}

export default UserNameEmail
Enter fullscreen mode Exit fullscreen mode
  • We have created a form with name and email input fields.
  • Using props, we are accessing the data object and handleChange method which we are going to create in our Main component along with other states.
  • Then we have passed the handleChange method to onChange event handlers and data values to the value attribute.

Dob and Gender

// DobGender.js
import React from 'react'

const DobGender = (props) => {
    const {data,handleChange} = props
    return (
        <div className="max-w-xs md:max-w-lg mx-auto">
            <form className="bg-white shadow-md rounded px-8 pt-6 pb-8 mb-4">
                <div className="inline-block relative w-64 mb-10">
                    <label className="block text-gray-700 text-sm font-bold mb-2" htmlFor="gender">
                        Gender
                    </label>
                    <select className="block w-full bg-gray-200 text-gray-700 border border-gray-200 rounded py-3 px-4 leading-tight focus:outline-none focus:bg-white focus:border-gray-500" name='gender' value={data.gender} onChange={handleChange}>
                        <option value="male">Male</option>
                        <option value="female">Female</option>
                    </select>
                </div>
                <div>
                    <label className="block text-gray-700 text-sm font-bold mb-2" htmlFor="dob">
                        Dob
                    </label>
                    <input className="appearance-none block w-full bg-gray-200 text-gray-700 border border-gray-200 rounded py-3 px-4 leading-tight focus:outline-none focus:bg-white focus:border-gray-500" name="dob" type="date"  value={data.dob} onChange={handleChange} />
                </div>
            </form>
        </div>
    )
}

export default DobGender
Enter fullscreen mode Exit fullscreen mode
  • We have created a form with gender as a select input field and dob as date input field.
  • Using props, we are accessing the data object and handleChange method which we are going to create in our Main component along with other states.
  • Then we have passed the handleChange method to onChange event handlers and data values to the value attribute.

Address

// Address.js
import { useState, useEffect } from 'react'

const Address = (props) => {
    const { data,setData } = props

    const [fullAddress, setFullAddress] = useState({
        house: "",
        locality: "",
        city: "",
        state: "",
        zip: "",
    })

    const handleAddressChange = (event) => {
        const { name, value } = event.target;
        setFullAddress({
            ...fullAddress,
            [name]: value,
        });
    };

    useEffect(() => {
        const isDataEmpty = () => {
            for (const key in fullAddress) {
                if (fullAddress[key].trim() === "") {
                    return true; // At least one property is empty
                }
            }
            return false; // All properties have values
        };
        if(!isDataEmpty()) {
            setData({
                ...data,
                address:fullAddress
            })
        }
    }, [fullAddress])

    return (
        <form className="w-full max-w-lg mx-auto bg-white shadow-md rounded px-8 pt-6 pb-8 mb-4">
            <div className="flex flex-wrap -mx-3 mb-6">
                <div className="w-full md:w-1/2 px-3 mb-6 md:mb-0">
                    <label className="block uppercase tracking-wide text-gray-700 text-xs font-bold mb-2" htmlFor="house-no">
                        House no.
                    </label>
                    <input 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" name="house" type="text" placeholder="house no." value={fullAddress.house} onChange={handleAddressChange} />
                </div>
                <div className="w-full md:w-1/2 px-3 mb-6 md:mb-0">
                    <label className="block uppercase tracking-wide text-gray-700 text-xs font-bold mb-2" htmlFor="locality">
                        Locality
                    </label>
                    <input 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" name="locality" type="text" placeholder="locality" value={fullAddress.locality} onChange={handleAddressChange} />
                </div>
            </div>

            <div className="flex flex-wrap -mx-3 mb-2">
                <div className="w-full md:w-1/3 px-3 mb-6 md:mb-0">
                    <label className="block uppercase tracking-wide text-gray-700 text-xs font-bold mb-2" htmlFor="city">
                        City
                    </label>
                    <input className="appearance-none block w-full bg-gray-200 text-gray-700 border border-gray-200 rounded py-3 px-4 leading-tight focus:outline-none focus:bg-white focus:border-gray-500" name="city" type="text" placeholder="city" value={fullAddress.city} onChange={handleAddressChange} />
                </div>
                <div className="w-full md:w-1/3 px-3 mb-6 md:mb-0">
                    <label className="block uppercase tracking-wide text-gray-700 text-xs font-bold mb-2" htmlFor="state">
                        State
                    </label>
                    <input className="appearance-none block w-full bg-gray-200 text-gray-700 border border-gray-200 rounded py-3 px-4 leading-tight focus:outline-none focus:bg-white focus:border-gray-500" name="state" type="text" placeholder="state" value={fullAddress.state} onChange={handleAddressChange} />
                </div>
                <div className="w-full md:w-1/3 px-3 mb-6 md:mb-0">
                    <label className="block uppercase tracking-wide text-gray-700 text-xs font-bold mb-2" htmlFor="zip">
                        Zip
                    </label>
                    <input className="appearance-none block w-full bg-gray-200 text-gray-700 border border-gray-200 rounded py-3 px-4 leading-tight focus:outline-none focus:bg-white focus:border-gray-500" name="zip" type="text" placeholder="zip code" value={fullAddress.zip} onChange={handleAddressChange} />
                </div>
            </div>
        </form>
    )
}

export default Address
Enter fullscreen mode Exit fullscreen mode
  • This one is interesting as the address has 5 input fields, we can't directly set the handler here, instead we have created another state with 5 properties for the full address and used this state to handle the input values and onChange handlers.
  • Then created a useEffect, checking whether all the properties of fullAddress object has values or not, if it is true then we will set the original data address property to the full address state.

Main component

// MainForm.js
"use client";
import { useState } from 'react'
import UserNameEmail from './UserNameEmail'
import DobGender from './DobGender'
import Address from './Address'

const MainForm = () => {
    const [data, setData] = useState({
        name: "",
        email: "",
        dob: "",
        gender: "male",
        address: "",
    })

    const handleChange = (event) => {
        const { name, value } = event.target;
        setData({
            ...data,
            [name]: value,
        });
    };


    const [activeTab, setActiveTab] = useState(0)

    const formElements = [
        <UserNameEmail data={data} handleChange={handleChange} />,
        <DobGender data={data} handleChange={handleChange} />,
        <Address data={data} setData={setData} />
    ]

    return (
        <div className='min-h-screen flex flex-col justify-center bg-slate-900'>
            <div>
                {
                    formElements[activeTab]
                }
            </div>
            <div className='flex flex-wrap gap-x-6 mx-auto'>
                <button
                    disabled={activeTab === 0 ? "disabled" : ""}
                    onClick={() => setActiveTab(prev => prev - 1)}
                    className={`px-4 py-2 rounded-xl bg-blue-600 text-white ${activeTab === 0 ? "opacity-50 bg-slate-600" : "opacity-100"}`}>
                    Back
                </button>
                <button
                    disabled={activeTab === formElements.length - 1 ? "disabled" : ""}
                    onClick={() => setActiveTab(prev => prev + 1)}
                    className={`px-4 py-2 rounded-xl bg-blue-600 text-white ${activeTab === formElements.length - 1 ? "opacity-50 bg-slate-600" : "opacity-100"}`}>Next</button>
                {
                    activeTab === formElements.length - 1 ? <button className='px-4 py-2 rounded-xl bg-blue-600 text-white' onClick={() => console.log(data)}>Submit</button> : null
                }
            </div>
        </div>
    )
}

export default MainForm
Enter fullscreen mode Exit fullscreen mode
  • Firstly we have imported out Form components
  • Then initializes a state variable called data using the useState hook. data is an object that represents the form data with default values for name, email, dob, gender, and address.
  • handleChange function is responsible for updating the form data when input fields change. It extracts the name and value properties from the event target (usually an input field) and updates the data state using the spread operator to merge the new value with the existing data.
  • Another state variable activeTab is initialized with a default value of 0. This variable is used to keep track of the currently active step/tab in the form.
  • An array called formElements is created, which contains JSX elements for the form steps. Each step is represented by a component (UserNameEmail, DobGender, and Address) with props that include the form data and the handleChange function.
  • We are using the activeTab state value with formElements array to render the component with a particular index value (0 -> UsernameEmail, 1 -> DobGender, 2 -> Address)
  • Then we have created 2 buttons, Back and Next, which will increment/decrement activeTab value by 1 to change the component, We are also disabling the Back button, if the activeTab is 0 and Disabling Next button if the activeTab is equal to the formElements array length - 1, which is the last element in the array.
  • Finally we have a submit button which will appear only when the form is at the last step, we simply put a console log statement for the submit button, you can handle the submittion according to your need.

Output -

Feel free to give suggestions in comments to improve the component and make it more reusable and efficient.
THANK YOU FOR CHECKING THIS POST
You can contact me on -
Instagram - https://www.instagram.com/supremacism__shubh/
LinkedIn - https://www.linkedin.com/in/shubham-tiwari-b7544b193/
Email - shubhmtiwri00@gmail.com

^^You can help me with some donation at the link below Thank you👇👇 ^^
☕ --> https://www.buymeacoffee.com/waaduheck <--

Also check these posts as well
https://dev.to/shubhamtiwari909/website-components-you-should-know-25nm

https://dev.to/shubhamtiwari909/smooth-scrolling-with-js-n56

https://dev.to/shubhamtiwari909/swiperjs-3802

https://dev.to/shubhamtiwari909/custom-tabs-with-sass-and-javascript-4dej

Top comments (4)

Collapse
 
mezieb profile image
Okoro chimezie bright

Nice work but i see you don't put (1) the output pictuure of the executed code (2) installation and start up instructions.if you leave it like that is like a generated code without evidence that is working,please if you add input/output it well be great.well done so far.

Collapse
 
shubhamtiwari909 profile image
Shubham Tiwari

Well would update the output , instead of images, would attach a sandbox if possible

Collapse
 
mezieb profile image
Okoro chimezie bright

yes that will be great,we are excited to experiment with you,well done!!!

Thread Thread
 
soloprogrammer profile image
SoloProgrammer

The sandbox link is not working!