React Forms Handling

Build robust forms with controlled inputs, uncontrolled refs, FormData extraction, and validation handling.

TL;DR

  1. Synchronize form inputs with component state using controlled input bindings.
  2. Extract raw multi-field form submissions easily using the native FormData API.
  3. Prevent default page reload cycles by intercepting the onSubmit event.

Controlled Inputs

    Single Controlled Input

    Bind input value to state and update on change.

    const [email, setEmail] = useState('');
    return (
      <InputBase
        value={email}
        onChange={(e) => setEmail(e.target.value)}
      />
    );
    Controlled Checkbox

    Bind boolean checked property rather than text value.

    const [agree, setAgree] = useState(false);
    return (
      <CheckboxBase
        checked={agree}
        onChange={(e) => setAgree(e.target.checked)}
      />
    );
    Controlled Select Dropdown

    Synchronize selected option value with state string.

    const [role, setRole] = useState('user');
    const onChange = (e: any) => setRole(e.target.value);
    return (
      <SelectBase value={role} onChange={onChange}>
        <option value="user">User</option>
        <option value="admin">Admin</option>
      </SelectBase>
    );

Multi-Field Form Management

    Single State Object Handler

    Handle multiple fields dynamically using input name attributes.

    const [form, setForm] = useState({
      first: '',
      last: '',
    });
    const handleChange = (
      e: React.ChangeEvent<HTMLInputElement>
    ) => {
      const { name, value } = e.target;
      setForm((prev) => ({ ...prev, [name]: value }));
    };
    Native FormData Extraction

    Read all form fields at once during submission without state.

    function handleSubmit(
      e: React.FormEvent<HTMLFormElement>
    ) {
      e.preventDefault();
      const data = new FormData(e.currentTarget);
      const payload = Object.fromEntries(data);
      console.log(payload);
    }
    Reset Form State

    Clear all inputs back to their original initial values.

    const handleReset = () => setForm(initialState);

Form Validation Patterns

    Inline Validation State

    Compute validation errors during rendering based on state.

    const emailValid = email.includes('@');
    const isErr = email && !emailValid;
    const error = isErr ? 'Invalid email' : null;
    Submit Validation Guard

    Validate all fields before executing network mutation.

    function onSubmit(e: React.FormEvent) {
      e.preventDefault();
      if (!form.email || !form.password) {
        setError('All fields required');
        return;
      }
      proceed();
    }
    Submit Button Disabling

    Disable submit button until form requirements are met.

    const isDisabled = !isValid || isSubmitting;
    return (
      <ButtonBase type="submit" disabled={isDisabled}>
        Save Changes
      </ButtonBase>
    );

Uncontrolled Forms with Refs

    Direct Ref Access

    Read input value only when submit button is pressed.

    const fileInputRef = useRef<HTMLInputElement>(null);
    const onUpload = () => {
      const files = fileInputRef.current?.files;
    };
    Default Value Uncontrolled

    Pre-populate uncontrolled input using defaultValue prop.

    <InputBase
      defaultValue="Initial Name"
      name="username"
    />
    Form Reset Native

    Reset all input DOM elements using HTMLFormElement.reset.

    formRef.current?.reset();

Tips

  1. Use controlled inputs when you need instant real-time validation or conditional UI disabling as the user interacts with inputs.
  2. Leverage native HTML attributes like required, min, and pattern alongside React handlers for accessible validation.

Warnings

  1. Switching an input from uncontrolled to controlled by passing undefined initial state causes React console errors.
  2. Avoid binding every keystroke to state if the form only needs data upon final submission; use FormData instead.

In Practice

FAQ