Form Submission and Loading States
In this page:
Tracking Submission State
A boolean state like submitting tracks whether the form is currently being processed. Setting it to true right when submission starts, and back to false once it finishes, lets the UI show a loading indicator and disable the button during that window.
Note: Name it isSubmitting or submitting so its purpose is obvious at a glance in JSX conditions.
Warning: If you forget to set submitting back to false after the request finishes, the button stays disabled forever.
Example: Tracking Submission State
<!DOCTYPE html>
<html>
<head>
<script src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
</head>
<body>
<div id="root"></div>
<script type="text/babel">
function App() {
const [submitting, setSubmitting] = React.useState(false);
const handleSubmit = e => {
e.preventDefault();
setSubmitting(true);
setTimeout(() => setSubmitting(false), 1000);
};
return <form onSubmit={handleSubmit}><button disabled={submitting}>{submitting ? "Submitting..." : "Submit"}</button></form>;
}
ReactDOM.createRoot(document.getElementById('root')).render(<App />);
</script>
</body>
</html>
Handling Success and Error Outcomes
A real submission can succeed or fail, so it's useful to track a result state (like idle, success, or error) in addition to the loading flag. This lets the UI show the right message once the request finishes, instead of just silently resetting.
Note: Reset the result state back to idle when the user starts editing the form again after a previous error.
Warning: Showing a stale success message after the user has already changed the form again can be confusing — clear it on new input.
Example: Handling Success and Error Outcomes
<!DOCTYPE html>
<html>
<head>
<script src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
</head>
<body>
<div id="root"></div>
<script type="text/babel">
function App() {
const [status, setStatus] = React.useState("idle");
const handleSubmit = e => {
e.preventDefault();
setStatus("submitting");
setTimeout(() => setStatus("success"), 800);
};
return (
<form onSubmit={handleSubmit}>
<button disabled={status === "submitting"}>Submit</button>
{status === "success" && <p>Saved successfully!</p>}
</form>
);
}
ReactDOM.createRoot(document.getElementById('root')).render(<App />);
</script>
</body>
</html>
Using try/catch/finally for Reliable Cleanup
Wrapping the async submission logic in try/catch/finally guarantees the loading state resets no matter what happens — whether the request succeeds, throws an error, or the network fails. The finally block always runs, making it the safest place to turn off the loading flag.
Note: Put setSubmitting(false) in finally, not duplicated in both the try and catch blocks, to avoid repeating yourself.
Warning: Without finally, an error thrown before you reach the success setSubmitting(false) line leaves the button permanently disabled.
Example: Using try/catch/finally for Reliable Cleanup
<!DOCTYPE html>
<html>
<head>
<script src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
</head>
<body>
<div id="root"></div>
<script type="text/babel">
function App() {
const [submitting, setSubmitting] = React.useState(false);
const [error, setError] = React.useState("");
const handleSubmit = async e => {
e.preventDefault();
setSubmitting(true);
try { throw new Error("Simulated failure"); }
catch (err) { setError(err.message); }
finally { setSubmitting(false); }
};
return <form onSubmit={handleSubmit}><button disabled={submitting}>Submit</button>{error && <p>{error}</p>}</form>;
}
ReactDOM.createRoot(document.getElementById('root')).render(<App />);
</script>
</body>
</html>
- Not disabling the submit button while a submission is in progress, allowing duplicate submits.
- Forgetting to reset the loading state in a catch block, leaving the button stuck disabled forever after an error.
- Not showing any success or error feedback after the submission finishes.
- A submitting state (boolean) tracks whether a form request is currently in flight.
- The submit button should be disabled while submitting to prevent duplicate submissions.
- try/catch/finally ensures the loading state resets whether the submission succeeds or fails.
- Showing distinct success and error messages gives the user clear feedback on the outcome.
No React-version restriction — built on standard async/await and useState.
Chapter Quiz — Complete all 8 topics to unlock
0/8 topics done
Complete these topics first: