Form handling usually has two main concerns:
- form submission
- form validation
A form in HTML submits automatically when the user clicks a submit button. In React, we usually prevent that default behavior and handle submission ourselves.
1) Form submission
By default, a button inside a form submits the form and refreshes the page. That is usually not what we want in React.
Option 1: use a button with type="button"
<button type="button" onClick={() => console.log('clicked')}>Save</button>
This prevents the browser from submitting the form.
Option 2: handle the form submit event
<form onSubmit={handleSubmit}>
<input name="username" />
<button type="submit">Submit</button>
</form>
Then in the handler:
const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
console.log('Form submitted');
};
This is the most common pattern in React apps.
React 19: form actions
React 19 introduces the idea of form actions as a newer pattern. In simple terms, the form can be submitted with server-like action handling instead of only onSubmit.
This is a newer approach, but the classic onSubmit + event.preventDefault() pattern is still the most common and easiest to understand when learning React.
2) Getting form values
There are several common ways to read form field values.
A) With state
This is the most common pattern for controlled inputs.
import { useState } from 'react';
export default function FormExample() {
const [name, setName] = useState('');
const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
console.log('Submitted name:', name);
};
return (
<form onSubmit={handleSubmit}>
<input
value={name}
onChange={(event) => setName(event.target.value)}
placeholder="Type your name"
/>
<button type="submit">Submit</button>
</form>
);
}
This is useful when we want to validate on every keystroke, show instant feedback, or use the value in the current render.
B) With refs
Refs are useful when we want to read values only when the form is submitted.
import { useRef } from 'react';
export default function FormExample() {
const nameRef = useRef<HTMLInputElement | null>(null);
const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
console.log('Submitted name:', nameRef.current?.value);
};
return (
<form onSubmit={handleSubmit}>
<input ref={nameRef} placeholder="Type your name" />
<button type="submit">Submit</button>
</form>
);
}
This pattern is less reactive, but it can be useful when we want less re-rendering and less state management.
C) With FormData
A very convenient pattern is to use the browser FormData API.
const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
const formData = new FormData(event.currentTarget);
const data = Object.fromEntries(formData.entries());
console.log(data);
};
Important: each input should have a name attribute so the values can be collected.
<form onSubmit={handleSubmit}>
<input name="username" />
<input name="email" />
<button type="submit">Submit</button>
</form>
This is a simple and clean way to collect all field values in one object.
3) Resetting the form
There are a few common ways to clear a form after submit.
Reset the form element directly
const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
event.currentTarget.reset();
};
This resets the DOM input values.
Use a reset button
<form>
<input name="username" />
<button type="reset">Reset</button>
</form>
This is useful when you want the browser to reset the fields automatically.
4) Form validation
Validation is about checking whether the user input is valid before we submit or save it.
There are two common approaches:
- validation with component state
- validation using refs or manual checks during submit
Validation with state
This is useful for real-time validation.
import { useState } from 'react';
export default function SignupForm() {
const [name, setName] = useState('');
const [error, setError] = useState('');
const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
if (name.trim().length < 3) {
setError('Name must be at least 3 characters long');
return;
}
setError('');
console.log('Valid form');
};
return (
<form onSubmit={handleSubmit}>
<input value={name} onChange={(e) => setName(e.target.value)} />
{error && <p>{error}</p>}
<button type="submit">Submit</button>
</form>
);
}
This pattern lets us validate as the user types and show error messages immediately.
Custom validation logic
Custom logic is usually written as plain JavaScript checks in the submit handler or in helper functions.
Examples:
- minimum length
- required field
- email format
- password strength
Built-in validation
HTML also supports built-in browser validation using attributes such as:
requiredminLengthtype="email"-
pattern
<form>
<input name="email" type="email" required />
<button type="submit">Submit</button>
</form>
This is simple and useful for basic validation, but React state validation is often more flexible for custom UX.
5) Summary
- forms submit by default, so use
event.preventDefault()when needed - use
onSubmitfor form submission handling - read values with state, refs, or
FormData - reset form values with
event.currentTarget.reset()or a reset button - validate either with custom code or built-in HTML validation
A good rule is:
- use controlled state for interactive validation and live UI
- use
FormDatafor simple submit-time gathering - use refs when you want less re-rendering and less state tracking
Top comments (0)