React Js

React JS Tutorial Learn React JS, the powerful JavaScript library for building dynamic and interactive user interfaces. This beginner-friendly tutorial covers core concepts like components, state, props, and hooks, with hands-on projects to create responsive web apps. Perfect for aspiring developers looking to master modern front-end development.

React Forms

24 April 2025 | Category:

Welcome to this beginner-friendly tutorial on React Forms! Forms are essential for capturing user input in React applications, enabling features like registration, search, or feedback submission. In this guide, you’ll learn how to create controlled forms, handle submissions, validate inputs, and manage dynamic fields. We’ll apply these concepts by building an Event Registration App that allows users to register for events with multiple ticket types. By the end, you’ll be confident building and managing forms in React to create interactive, user-friendly applications.


What are React Forms?

React forms are UI components that collect user input through elements like text inputs, checkboxes, or select dropdowns. Unlike traditional HTML forms, React forms are typically controlled, meaning the form’s data is managed by React state, ensuring a single source of truth and predictable behavior. Forms in React handle user interactions via events and can include validation to ensure data quality.

Why Learn React Forms?

  • User Interaction: Capture and process user input for registrations, searches, or settings.
  • Dynamic UI: Update the UI based on form data, like showing errors or adding fields.
  • Data Validation: Ensure user input meets requirements before submission.
  • Core Skill: Forms are used in most React applications, from e-commerce to dashboards.

Prerequisites

Before starting, you should have:

  • Basic knowledge of React (components, props, state, JSX, events, conditional rendering, lists) and JavaScript (ES6).
  • Node.js and npm installed (download from nodejs.org).
  • A code editor like Visual Studio Code.
  • A terminal for running commands.

We’ll use Create React App to set up our project and Tailwind CSS for responsive, attractive styling, consistent with your preference for visually appealing, responsive designs.


Key Form Concepts

Let’s explore the core concepts for building forms in React.

1. Controlled Components

A controlled component ties an input’s value to React state, updating state on every change via an onChange handler.

Example:

const Input = () => {
  const [value, setValue] = React.useState('');
  return (
    <input
      value={value}
      onChange={(e) => setValue(e.target.value)}
      placeholder="Type here"
    />
  );
};

2. Form Submission

Handle form submissions with an onSubmit event, using e.preventDefault() to prevent the default browser refresh.

Example:

const Form = () => {
  const [name, setName] = React.useState('');
  const handleSubmit = (e) => {
    e.preventDefault();
    console.log('Submitted:', name);
  };
  return (
    <form onSubmit={handleSubmit}>
      <input value={name} onChange={(e) => setName(e.target.value)} />
      <button type="submit">Submit</button>
    </form>
  );
};

3. Input Validation

Validate user input in real-time or on submission, displaying error messages conditionally.

Example:

const Form = () => {
  const [email, setEmail] = React.useState('');
  const [error, setError] = React.useState('');

  const handleSubmit = (e) => {
    e.preventDefault();
    if (!email.includes('@')) {
      setError('Invalid email');
    } else {
      setError('');
      console.log('Submitted:', email);
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <input value={email} onChange={(e) => setEmail(e.target.value)} />
      {error && <p className="text-red-500">{error}</p>}
      <button type="submit">Submit</button>
    </form>
  );
};

4. Dynamic Form Fields

Manage multiple or dynamic inputs (e.g., adding/removing fields) using arrays in state.

Example:

const Form = () => {
  const [fields, setFields] = React.useState(['']);

  const addField = () => setFields([...fields, '']);
  const updateField = (index, value) =>
    setFields(fields.map((f, i) => (i === index ? value : f)));

  return (
    <form>
      {fields.map((field, index) => (
        <input
          key={index}
          value={field}
          onChange={(e) => updateField(index, e.target.value)}
        />
      ))}
      <button type="button" onClick={addField}>
        Add Field
      </button>
    </form>
  );
};

5. Common Form Elements

React supports various form elements, each with specific event handlers:

  • Text Inputs: <input type="text" /> with onChange.
  • Checkboxes: <input type="checkbox" /> with checked and onChange.
  • Select Dropdowns: <select> with value and onChange.
  • Textareas: <textarea> with value and onChange.

Example:

const Form = () => {
  const [checked, setChecked] = React.useState(false);
  return (
    <input
      type="checkbox"
      checked={checked}
      onChange={(e) => setChecked(e.target.checked)}
    />
  );
};

Setting Up the Project

Let’s create a React app to build our Event Registration App, which will use forms to collect user data and manage ticket selections.

  1. Create a New React App:
    Open your terminal and run: npx create-react-app event-registration cd event-registration
  2. Install Tailwind CSS:
    Install and configure Tailwind CSS for responsive styling: npm install -D tailwindcss npx tailwindcss init Update tailwind.config.js: /** @type {import('tailwindcss').Config} */ module.exports = { content: ['./src/**/*.{js,jsx,ts,tsx}'], theme: { extend: {} }, plugins: [] }; Update src/index.css: @tailwind base; @tailwind components; @tailwind utilities; body { font-family: Arial, sans-serif; margin: 0; background-color: #f5f5f5; }
  3. Start the Development Server: npm start This opens your app at http://localhost:3000.
  4. Clean Up:
    Open src/App.js and replace its content with: const App = () => { return ( <div className="container mx-auto p-4"> <h1 className="text-3xl font-bold text-gray-800">Event Registration</h1> </div> ); }; export default App; Delete src/App.css and src/logo.svg.

Building the Event Registration App

Our Event Registration App will:

  • Use a form to collect user details (name, email) and ticket selections via a RegistrationForm component.
  • Allow users to add multiple ticket types (e.g., General, VIP) with quantities, dynamically managing fields.
  • Validate inputs and display errors, preventing submission of invalid data.
  • Show a confirmation message with registration details after submission.
  • Use Tailwind CSS for a responsive, visually appealing design, aligning with your preference for attractive UI.

Step 1: Create the RegistrationForm Component

  1. In src, create a file named RegistrationForm.js:const RegistrationForm = ({ onSubmit }) => { const [formData, setFormData] = React.useState({ name: '', email: '', tickets: [{ type: 'General', quantity: 0 }] }); const [errors, setErrors] = React.useState({}); const handleChange = (e, index) => { const { name, value } = e.target; if (name.startsWith('ticket')) { const tickets = [...formData.tickets]; tickets[index] = { ...tickets[index], quantity: parseInt(value) || 0 }; setFormData({ ...formData, tickets }); } else { setFormData({ ...formData, [name]: value }); } }; const addTicket = () => { setFormData({ ...formData, tickets: [...formData.tickets, { type: 'General', quantity: 0 }] }); }; const updateTicketType = (index, type) => { const tickets = [...formData.tickets]; tickets[index] = { ...tickets[index], type }; setFormData({ ...formData, tickets }); }; const removeTicket = (index) => { const tickets = formData.tickets.filter((_, i) => i !== index); setFormData({ ...formData, tickets }); }; const validateForm = () => { const newErrors = {}; if (!formData.name.trim()) newErrors.name = 'Name is required'; if (!formData.email.includes('@')) newErrors.email = 'Invalid email'; if (formData.tickets.every((t) => t.quantity === 0)) newErrors.tickets = 'At least one ticket is required'; setErrors(newErrors); return Object.keys(newErrors).length === 0; }; const handleSubmit = (e) => { e.preventDefault(); if (validateForm()) { onSubmit(formData); setFormData({ name: '', email: '', tickets: [{ type: 'General', quantity: 0 }] }); setErrors({}); } }; return ( <form onSubmit={handleSubmit} className="max-w-lg"> <div className="mb-4"> <label className="block text-gray-700 mb-1">Name</label> <input type="text" name="name" value={formData.name} onChange={handleChange} className="w-full p-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" placeholder="Your name" /> {errors.name && <p className="text-red-500 text-sm">{errors.name}</p>} </div> <div className="mb-4"> <label className="block text-gray-700 mb-1">Email</label> <input type="email" name="email" value={formData.email} onChange={handleChange} className="w-full p-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" placeholder="Your email" /> {errors.email && <p className="text-red-500 text-sm">{errors.email}</p>} </div> <div className="mb-4"> <label className="block text-gray-700 mb-1">Tickets</label> {formData.tickets.map((ticket, index) => ( <div key={index} className="flex items-center space-x-2 mb-2"> <select value={ticket.type} onChange={(e) => updateTicketType(index, e.target.value)} className="p-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" > <option value="General">General ($50)</option> <option value="VIP">VIP ($100)</option> </select> <input type="number" name={`ticket-${index}`} value={ticket.quantity} onChange={(e) => handleChange(e, index)} min="0" className="w-20 p-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" /> {formData.tickets.length > 1 && ( <button type="button" onClick={() => removeTicket(index)} className="text-red-500 hover:text-red-700" > Remove </button> )} </div> ))} {errors.tickets && ( <p className="text-red-500 text-sm">{errors.tickets}</p> )} <button type="button" onClick={addTicket} className="text-blue-500 hover:text-blue-700 mt-2" > + Add Another Ticket </button> </div> <button type="submit" className="bg-blue-500 text-white px-4 py-2 rounded-md hover:bg-blue-600" > Register </button> </form> ); }; export default RegistrationForm;
    • Form Features:
      • Controlled inputs for name, email, and dynamic tickets (type and quantity).
      • Handles form submission with validation, preventing submission if invalid.
      • Supports dynamic fields by adding/removing ticket entries with unique keys.
      • Displays error messages conditionally using &&.
      • Styled with Tailwind CSS for a responsive, clean layout.

Step 2: Create the RegistrationSummary Component

  1. In src, create a file named RegistrationSummary.js:const RegistrationSummary = ({ registrations }) => { return ( <div className="mt-6 max-w-lg"> <h2 className="text-2xl font-semibold text-gray-800 mb-4"> Registered Users </h2> {registrations.length > 0 ? ( <ul className="space-y-4"> {registrations.map((reg, index) => ( <li key={index} className="bg-white p-4 rounded-lg shadow-md" > <p className="font-semibold">{reg.name}</p> <p className="text-gray-600">{reg.email}</p> <p className="text-gray-600"> Tickets:{' '} {reg.tickets .filter((t) => t.quantity > 0) .map((t) => `${t.quantity} ${t.type}`) .join(', ')} </p> </li> ))} </ul> ) : ( <p className="text-gray-500">No registrations yet.</p> )} </div> ); }; export default RegistrationSummary;
    • Form Features:
      • Displays a list of registrations, rendering only non-zero ticket quantities.
      • Uses conditional rendering for empty states (registrations.length > 0 ? ... : ...).
      • Applies unique keys for list items (key={index} is safe here since registrations are static).
      • Styled with Tailwind CSS for a responsive, card-based layout.

Step 3: Update the App Component

  1. Update src/App.js to manage registrations and render the form and summary:import RegistrationForm from './RegistrationForm'; import RegistrationSummary from './RegistrationSummary'; const App = () => { const [registrations, setRegistrations] = React.useState([]); const handleSubmit = (formData) => { setRegistrations([...registrations, formData]); }; return ( <div className="container mx-auto p-4"> <h1 className="text-3xl font-bold text-gray-800 mb-6"> Event Registration </h1> <RegistrationForm onSubmit={handleSubmit} /> <RegistrationSummary registrations={registrations} /> </div> ); }; export default App;
    • Form Features:
      • Manages the list of registrations in state, updated via handleSubmit.
      • Passes onSubmit as a prop to RegistrationForm to collect form data.
      • Renders RegistrationSummary to display submitted registrations.
      • Uses Tailwind CSS for a responsive, centered layout.

Step 4: Test the App

  • Save all files and ensure the development server is running (npm start).
  • Open http://localhost:3000. You should see:
    • A form with fields for name, email, and one ticket selection (General or VIP).
    • Options to add more ticket fields or remove extras.
    • Validation errors if name is empty, email is invalid, or no tickets are selected.
    • A summary section that updates with each valid submission, showing name, email, and tickets.
  • Try:
    • Submitting with invalid data (e.g., no name) to see error messages.
    • Adding multiple tickets (e.g., 2 General, 1 VIP) and submitting.
    • Checking the summary for correct ticket display.
    • Verifying responsiveness by resizing the browser or using mobile view in dev tools.

Understanding the Code

Let’s recap how forms power our Event Registration App:

  • Controlled Components: RegistrationForm uses state (formData) to control name, email, and tickets, updating via handleChange.
  • Form Submission: handleSubmit prevents default refresh, validates data, and passes valid data to App via onSubmit.
  • Input Validation: validateForm checks for required fields and valid email, displaying errors conditionally.
  • Dynamic Fields: tickets is an array in state, with addTicket and removeTicket managing entries and updateTicketType handling dropdowns.
  • Responsive Design: Tailwind CSS ensures a mobile-friendly layout with clean inputs and cards, aligning with your preference for attractive, responsive UI.
  • ES6 Features:
    • Arrow functions for components and handlers.
    • Destructuring ({ name, value }, formData.tickets).
    • Spread operator for state updates ([...formData.tickets]).
    • Modules for component organization.

Best Practices for React Forms

  • Use Controlled Components: Tie inputs to state for predictable behavior:<input value={value} onChange={(e) => setValue(e.target.value)} />
  • Prevent Default Submission: Always use e.preventDefault() in onSubmit to avoid page refreshes.
  • Validate Early: Show errors during input (real-time) or on submission to guide users:{error && <p>{error}</p>}
  • Use Unique Keys for Dynamic Fields: Ensure dynamic inputs have unique key props (e.g., key={index} for tickets).
  • Keep Forms Accessible: Add labels, placeholders, and error messages for usability:<label>Name</label> <input aria-invalid={!!error} />
  • Optimize Performance: Memoize handlers with useCallback for complex forms:const handleChange = React.useCallback((e) => {}, []);

Common Form Pitfalls and Fixes

  • Uncontrolled Inputs:
    Problem: Not tying value to state causes unpredictable behavior.
    Fix: Make inputs controlled:<input value={state} onChange={(e) => setState(e.target.value)} />
  • Missing preventDefault:
    Problem: Form submission refreshes the page.
    Fix: Add e.preventDefault() in handleSubmit.
  • Invalid Dynamic Keys:
    Problem: Using index as a key for dynamic fields causes issues when reordering.
    Fix: Use stable IDs or ensure fields are static (our app uses index safely for tickets).
  • Poor Validation UX:
    Problem: Errors only shown on submit can frustrate users.
    Fix: Validate on change for key fields:const handleChange = (e) => { setValue(e.target.value); if (!e.target.value) setError('Required'); };

Forms in Functional vs. Class Components

Since you’ve explored class components previously, here’s how form handling differs:

  • Functional Components: Use useState for form state, with handlers defined as functions:const Form = () => { const [value, setValue] = React.useState(''); return <input value={value} onChange={(e) => setValue(e.target.value)} />; };
  • Class Components: Use this.state and this.setState, binding handlers in the constructor:class Form extends React.Component { constructor(props) { super(props); this.state = { value: '' }; this.handleChange = this.handleChange.bind(this); } handleChange(e) { this.setState({ value: e.target.value }); } render() { return <input value={this.state.value} onChange={this.handleChange} />; } }

Our app uses functional components for simplicity and alignment with modern React, but form concepts apply to both.


What’s Next?

You’ve built an Event Registration App using React forms! Here are some next steps:

  • Add Features: Include a checkbox for agreeing to terms, with validation.
  • Learn More Techniques: Explore useReducer for complex form state or libraries like Formik for form management.
  • Enhance Styling: Add animations with Tailwind CSS or Framer Motion for smoother form transitions.
  • Build Another App: Create a survey form or booking system to practice forms.

Practice Challenge

Add a “Clear Form” button to RegistrationForm that resets all fields to their initial state without submitting. Ensure it doesn’t affect the registrations list.


Resources

Congratulations on mastering React forms! You’re now equipped to build interactive, user-friendly forms in your applications. Keep practicing and happy coding!