REACT Lesson 12 – Forms in React | Dataplexa
Lesson 12

Forms in React

Build a search filter component for DataFlow's analytics dashboard with form inputs, validation, and user feedback.

Forms make your React apps interactive. Every search box, login screen, and settings panel needs form handling. React handles forms differently than regular HTML — forms become part of your component's state instead of just sitting in the DOM. Think about DataFlow's filter bar. Users type search terms, pick date ranges, and select categories. Each keystroke updates the interface instantly. That's React forms in action.

Form Elements in JSX

HTML forms use attributes like value and defaultValue. JSX is similar but uses camelCase for attributes and curly braces for dynamic values.
// DataFlow search component - basic form structure
function DataFlowSearch() {
  return (
    <form>
      <input 
        type="text" 
        placeholder="Search transactions..."
        className="search-input"
      />
      <select className="category-select">
        <option value="">All Categories</option>
        <option value="revenue">Revenue</option>
        <option value="expenses">Expenses</option>
      </select>
    </form>
  );
}
DataFlow Dashboard

What just happened?

React rendered a static form with HTML elements. But typing doesn't update any state yet — the form exists but doesn't communicate with your component. Try typing in the search box.

Notice the form renders but doesn't do anything interactive yet. That's because React needs explicit connections between form elements and component state.

Handling Form Events

Forms trigger events when users interact with them. The most common are onChange for input changes and onSubmit for form submission.
// DataFlow search with event handling
function DataFlowSearch() {
  function handleInputChange(event) {
    console.log('User typed:', event.target.value);
  }

  function handleSubmit(event) {
    event.preventDefault(); // Stop page reload
    console.log('Form submitted!');
  }

  return (
    <form onSubmit={handleSubmit}>
      <input 
        type="text" 
        placeholder="Search transactions..."
        onChange={handleInputChange}
      />
      <button type="submit">Search</button>
    </form>
  );
}
DataFlow Dashboard

What just happened?

React captured form events and prevented the default browser behavior. The event.preventDefault() stops the page from refreshing on form submit. Try typing and clicking Search — watch the console logs appear.

The event.target.value gives you the current input value. But this approach doesn't store the value anywhere in your component. React calls this "uncontrolled" because the DOM manages the input's value.

Form State with useState

Real form handling needs state to store user input. Each form field gets its own state variable, or you can use an object for multiple fields.
// DataFlow search with state management
function DataFlowSearch() {
  const [searchTerm, setSearchTerm] = React.useState('');
  const [category, setCategory] = React.useState('');

  function handleSubmit(event) {
    event.preventDefault();
    console.log('Searching for:', searchTerm, 'in', category);
  }

  return (
    <form onSubmit={handleSubmit}>
      <input 
        type="text" 
        value={searchTerm}
        onChange={(e) => setSearchTerm(e.target.value)}
        placeholder="Search transactions..."
      />
      <select 
        value={category} 
        onChange={(e) => setCategory(e.target.value)}
      >
        <option value="">All Categories</option>
        <option value="revenue">Revenue</option>
        <option value="expenses">Expenses</option>
      </select>
      <button type="submit">Search</button>
    </form>
  );
}
DataFlow Dashboard

What just happened?

React now controls the form values through state. Each keystroke triggers a state update, which re-renders the component with the new value. Notice how the current values display in real-time below the form.

This pattern where React controls the input value through state is called a "controlled component." The input's value always matches your component's state.

Multiple Form Fields

Real forms have many fields. You can manage them individually or group related data into a single state object. Object state works well for forms with many related fields.
// DataFlow user settings form with multiple fields
function UserSettingsForm() {
  const [settings, setSettings] = React.useState({
    name: 'Sarah Chen',
    email: 'sarah@dataflow.com',
    notifications: true,
    theme: 'dark'
  });

  function handleChange(event) {
    const { name, value, type, checked } = event.target;
    setSettings(prev => ({
      ...prev,
      [name]: type === 'checkbox' ? checked : value
    }));
  }

  return (
    <form>
      <input 
        name="name"
        value={settings.name}
        onChange={handleChange}
        placeholder="Full Name"
      />
      <input 
        name="email"
        type="email"
        value={settings.email}
        onChange={handleChange}
        placeholder="Email"
      />
      <label>
        <input 
          name="notifications"
          type="checkbox"
          checked={settings.notifications}
          onChange={handleChange}
        /> Email notifications
      </label>
    </form>
  );
}
DataFlow Dashboard

What just happened?

One handleChange function manages all form fields using the input's name attribute. The spread operator ...prev keeps existing values while updating just the changed field. Try editing any field and watch the JSON update.

The [name]: value syntax uses computed property names — the input's name becomes the object key. This pattern scales well as you add more form fields.

Form Validation

Production forms need validation to catch errors before submission. You can validate on every change, on blur, or just before submit. Real-time validation provides the best user experience.
// DataFlow signup form with validation
function SignupForm() {
  const [form, setForm] = React.useState({
    email: '',
    password: ''
  });
  const [errors, setErrors] = React.useState({});

  function validateField(name, value) {
    if (name === 'email') {
      return value.includes('@') ? '' : 'Valid email required';
    }
    if (name === 'password') {
      return value.length >= 8 ? '' : 'Password must be 8+ characters';
    }
    return '';
  }

  function handleChange(event) {
    const { name, value } = event.target;
    setForm(prev => ({ ...prev, [name]: value }));
    
    const error = validateField(name, value);
    setErrors(prev => ({ ...prev, [name]: error }));
  }

  return (
    <form>
      <input 
        name="email"
        value={form.email}
        onChange={handleChange}
        placeholder="Email"
      />
      {errors.email && <span style={{color: 'red'}}>{errors.email}</span>}
      
      <input 
        name="password"
        type="password"
        value={form.password}
        onChange={handleChange}
        placeholder="Password"
      />
      {errors.password && <span style={{color: 'red'}}>{errors.password}</span>}
    </form>
  );
}
DataFlow Dashboard

What just happened?

Validation runs on every keystroke, immediately showing or hiding error messages. The component manages both form data and error states separately. Try typing incomplete email addresses or short passwords to see validation in action.

Validation state lives alongside form state. Keep them separate so you can validate individual fields without affecting others. Users appreciate immediate feedback instead of waiting until submit.

Common Form Patterns

Here are patterns you'll use in every React app:

Reset Forms

Set state back to initial values after successful submit.

Disable Submit

Check if form is valid or submitting to disable the submit button.

Loading States

Show spinners or change button text while processing form submission.

Field Arrays

Dynamic lists where users can add/remove items like tags or addresses.

// Complete DataFlow transaction form with common patterns
function TransactionForm() {
  const [form, setForm] = React.useState({
    amount: '',
    description: '',
    category: 'revenue'
  });
  const [isSubmitting, setIsSubmitting] = React.useState(false);

  const isValid = form.amount && form.description;

  async function handleSubmit(event) {
    event.preventDefault();
    if (!isValid) return;
    
    setIsSubmitting(true);
    // Simulate API call
    await new Promise(resolve => setTimeout(resolve, 1000));
    setIsSubmitting(false);
    
    // Reset form after success
    setForm({ amount: '', description: '', category: 'revenue' });
  }

  function handleChange(event) {
    const { name, value } = event.target;
    setForm(prev => ({ ...prev, [name]: value }));
  }

  return (
    <form onSubmit={handleSubmit}>
      <input 
        name="amount"
        type="number"
        value={form.amount}
        onChange={handleChange}
        placeholder="Amount ($)"
      />
      <input 
        name="description"
        value={form.description}
        onChange={handleChange}
        placeholder="Description"
      />
      <select name="category" value={form.category} onChange={handleChange}>
        <option value="revenue">Revenue</option>
        <option value="expenses">Expenses</option>
      </select>
      <button 
        type="submit" 
        disabled={!isValid || isSubmitting}
      >
        {isSubmitting ? 'Adding...' : 'Add Transaction'}
      </button>
    </form>
  );
}
DataFlow Dashboard

What just happened?

The form demonstrates professional patterns — validation disables submit, loading states prevent double-clicks, and successful submission resets the form. Fill out both required fields to enable the submit button, then watch the loading state and reset behavior.

Production forms need these details. Users expect buttons to disable during submission and forms to reset after success. The extra code prevents common bugs and improves user experience significantly. React forms follow the same principles as other React features — state controls everything, events update state, and the UI reflects the current state. Master these patterns and you can handle any form requirement.

Quiz

1. The DataFlow team needs to prevent their search form from refreshing the page when submitted. What method should they call in the submit handler?


2. What makes a form input "controlled" in React?


3. DataFlow has a checkbox for "Show advanced filters". Which prop controls whether it appears checked?


Up Next: Controlled Components

Master the difference between controlled and uncontrolled components, and learn when to use each pattern for optimal form performance.