The Secret Life of React Forms: Controlling the Uncontrollable with Validation Superpowers! (React Day 6)

Published: (January 13, 2026 at 11:30 PM EST)
6 min read
Source: Dev.to

Source: Dev.to

Why Forms Matter: A Quick Reality Check

Picture this: you’re building a killer e‑commerce app. Users need to sign up, log in, or checkout with payment details. Without solid form handling, data gets lost, errors pop up randomly, and users bail.

In React, forms aren’t just HTML tags – they’re dynamic, state‑driven powerhouses. We’ll cover the basics, but with real‑world twists like a user‑registration scenario where bad validation could leak invalid emails into your database. Fun, right?


Controlled vs. Uncontrolled Components: The Great Debate

In React, form elements like <input> can be controlled or uncontrolled.

TypeDescriptionProsCons
Controlled ComponentsReact calls the shots. You tie the input’s value to state via useState, and update it on every change with an onChange handler.• Total control
• Easy validation
• Real‑time feedback
• A bit more code
• Potential performance hits on huge forms
Uncontrolled ComponentsLet the DOM handle it old‑school. Use refs (e.g., useRef) to grab values on submit.• Simpler for quick forms
• Fewer re‑renders
• Harder to validate live
• Less “React‑y” feel

Scenario – Imagine a search bar:

  • Controlled? Update suggestions as the user types.
  • Uncontrolled? Just grab the query on Enter – fine for basics, but you miss that instant magic.

Controlled component example

import { useState } from 'react';

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

Uncontrolled component example

import { useRef } from 'react';

function UncontrolledInput() {
  const inputRef = useRef(null);
  const handleSubmit = () => console.log(inputRef.current?.value);
  return (
    <>
      <input ref={inputRef} placeholder="Type something..." />
      <button onClick={handleSubmit}>Log Value</button>
    </>
  );
}

Diagram – Visualize the flow:

Controlled vs Uncontrolled Component in React

Common pitfall: Forgetting to handle onChange in controlled mode – the input becomes read‑only!

UX tip: Use controlled components for anything needing live updates, like password‑strength meters.


Form Handling: From Input to Submission

Handling forms is all about events and state. Start with a <form> element, add inputs, and slap an onSubmit handler on the form. Prevent the default page reload with e.preventDefault().

Real‑time example – a login form

import { useState } from 'react';

function LoginForm() {
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');

  const handleSubmit = (e) => {
    e.preventDefault();
    // API call here
    console.log({ email, password });
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        type="email"
        value={email}
        onChange={(e) => setEmail(e.target.value)}
        placeholder="Email"
        required
      />
      <input
        type="password"
        value={password}
        onChange={(e) => setPassword(e.target.value)}
        placeholder="Password"
        required
      />
      <button type="submit">Login</button>
    </form>
  );
}

Pitfall: Not resetting state after submit – users see old data.

UX win: Add loading spinners during submission for that pro feel.


Validation Strategies: Don’t Let Bad Data Crash the Party

Validation is where forms get serious. Strategies include:

  • Inline – validate as the user types.
  • On blur – validate when the field loses focus.
  • On submit – validate once the user tries to submit.

Simple vanilla validation example

import { useState } from 'react';

function SignupForm() {
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');
  const [errors, setErrors] = useState({});

  const validate = () => {
    const newErrors = {};
    if (!email.includes('@')) newErrors.email = 'Invalid email!';
    if (password.length < 6) newErrors.password = 'Password too short!';
    setErrors(newErrors);
    return Object.keys(newErrors).length === 0;
  };

  const handleSubmit = (e) => {
    e.preventDefault();
    if (validate()) {
      // Proceed with API call
      console.log('Form is valid');
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        type="email"
        value={email}
        onChange={(e) => setEmail(e.target.value)}
        placeholder="Email"
        required
      />
      {errors.email && <p style={{ color: 'red' }}>{errors.email}</p>}

      <input
        type="password"
        value={password}
        onChange={(e) => setPassword(e.target.value)}
        placeholder="Password"
        required
      />
      {errors.password && <p style={{ color: 'red' }}>{errors.password}</p>}

      <button type="submit">Sign Up</button>
    </form>
  );
}

Illustration of validation in action:

Illustration of validation in action

Pitfall: Over‑validating – don’t nag users on every keystroke.

UX tip: Show friendly error messages in red, with icons for clarity.


Why reinvent the wheel? Libraries make life easier.

LibraryProsConsBest For
Vanilla React• Lightweight, no dependencies
• Full control over implementation
• More boilerplate code
• Need to handle many concerns manually
Small‑to‑medium forms, learning fundamentals
Formik• Handles state, validation, and submission out of the box
• Great ecosystem (Yup integration)
• Adds bundle size
• Learning curve for its API
Complex forms with many fields
React Hook Form• Minimal re‑renders, excellent performance
• Simple API, works well with existing UI libs
• Slightly different mental model (uncontrolled‑by‑default)Large forms, performance‑critical apps
Redux‑Form (legacy)• Centralized form state in Redux
• Good for apps already using Redux heavily
• Deprecated in favor of newer libs
• Can bloat Redux store
Legacy projects already on Redux‑Form

Takeaways

  • Controlled components give you granular control and are ideal for live validation.
  • Uncontrolled components are quick to set up but trade off real‑time insight.
  • Always prevent default submission and manage state yourself.
  • Choose a validation strategy that balances UX and data integrity.
  • When forms become complex, consider a library like Formik or React Hook Form to reduce boilerplate.

Now go forth and build rock‑solid, user‑friendly forms that make your React apps shine! 🎉

Form Libraries Overview

Formik

  • Handles state, validation, touched/dirty flags.
  • A bit heavy; often paired with Yup for schema validation.
  • Ideal for complex forms with many fields.

React Hook Form

  • Highly performant; uses uncontrolled inputs by default.
  • Simple validation API; integrates well with native HTML validation.
  • Slight learning curve if you’re new to hooks.
  • Perfect for high‑perf apps where you want to minimise re‑renders.

Example

/* Formik */
import { Formik, Form, Field } from 'formik';
import * as Yup from 'yup';

/* React Hook Form */
import { useForm } from 'react-hook-form';

Scenario – For a multi‑step survey, React Hook Form shines because it requires less boilerplate and fewer re‑renders.

Watch the comparison: React Hook Form vs Formik (YouTube) (replace with a real video link)


Accessibility Best Practices: Forms for Everyone

Inclusivity matters! Always pair inputs with proper labeling and ARIA attributes, and manage focus correctly.

  • Label each input: <label htmlFor="..."> (or wrap the input).
  • Error handling: add aria-invalid="true" and aria-describedby="error-id" on invalid fields.
  • Keyboard navigation: ensure a logical tab order and avoid focus traps.

Scenario – A screen‑reader user filling out a contact form will be lost without appropriate ARIA markup.

Visual Reference

React Native accessibility best practices for inclusive apps

Pitfall: Ignoring colour contrast in error messages.
UX tip: Test with tools like WAVE or axe to catch contrast issues.


Live Examples & Wrapping Up

Try it yourself – A validated signup form on CodeSandbox:
Open CodeSandbox (replace with a real link if needed)

In a real‑world app (e.g., editing a user profile), you’ll typically combine:

  1. Controlled inputs for immediate UI feedback.
  2. Validation on blur or submit.
  3. Accessible labels and ARIA attributes.
  4. A form library (Formik or React Hook Form) when the form scales.

That’s a form‑tastic ride! What’s the trickiest form you’ve built? Drop your story below.

Stay tuned for Day 7: Routing and Navigation Adventures. Happy coding! 🌟

Back to Blog

Related posts

Read more »

React Coding Challenge : Card Flip Game

React Card Flip Game – Code tsx import './styles.css'; import React, { useState, useEffect } from 'react'; const values = 1, 2, 3, 4, 5; type Card = { id: numb...