How to Stop a React Form From Reloading the Page
A React form reloads the page on submit because the browser's default form action - a GET navigation to the same URL - fires before React's state update runs. One call to e.preventDefault() stops the navigation and lets React handle everything in JavaScript.
Why the page reloads
A <form> submit fires two things in order: the browser's default action
(navigate to the action URL, or the current URL if none is set, encoding the
inputs as a query string) and then any JavaScript onSubmit handler. The browser
navigation happens first - it reloads the page, React re-mounts with fresh state,
and the items or input values you set in the handler are gone.
This is why you see the URL flash a ?item=Milk query string right before the
list empties: that's the browser doing a GET to the same route.
The fix: call e.preventDefault()
In your submit handler, call e.preventDefault() as the very first thing. It
cancels the browser navigation so only your JavaScript runs.
import { useState } from 'react';
export default function App() {
const [items, setItems] = useState<string[]>([]);
const [text, setText] = useState('');
function handleSubmit(e: React.FormEvent) {
e.preventDefault(); // cancel the browser navigation
setItems((prev) => [...prev, text]); // update React state
setText(''); // clear the input
}
return (
<form onSubmit={handleSubmit}>
<input
value={text}
onChange={(e) => setText(e.target.value)}
placeholder="Add an item..."
/>
<button type="submit">Add</button>
<ul>
{items.map((it, i) => <li key={i}>{it}</li>)}
</ul>
</form>
);
}
The e argument is a React.FormEvent (a synthetic event wrapping the native
browser event). preventDefault() exists on every event type -
React.FormEvent, React.MouseEvent, React.KeyboardEvent - so the same
pattern applies wherever you need to stop the browser's default behavior.
What the browser default actually does
Without preventDefault():
- Browser serializes the form fields:
?item=Milk - Browser navigates to
http://localhost:5173/?item=Milk - React unmounts and re-mounts - all state resets to initial values
- Your
setItems(...)call either never runs or runs against the now-reset state on the previous mount
With preventDefault(), step 1-3 never happen, and only your handler runs.
When preventDefault is not enough
- Button outside the form - if the submit
<button>sits outside the<form>tag,onSubmitmay never fire. Keep the button inside, or give itform="form-id"and addid="form-id"to the form element. - Missing type="submit" - a
<button>without an explicittypedefaults totype="submit"inside a form, which is usually what you want. A button withtype="button"does nothing on its own. - Form libraries - React Hook Form, Formik, and similar libraries call
preventDefault()internally, but you still need to understand what they're suppressing when behavior is unexpected.
Want to try it hands-on? HeyDevJob gives you this exact setup in a live cloud workspace in your browser - edit it, run it, and see it work. Free, nothing to install.
Try it in a workspace →What you'll practice
- Calling e.preventDefault() to stop browser form navigation in React
- Typing the submit event as React.FormEvent in TypeScript
- Managing controlled input state with useState + onChange
FAQ
Why does my React form reload the page on submit?
A <form> submit triggers the browser's default navigation action - it encodes the inputs as a query string and does a GET to the same URL, which reloads the page and resets all React state. Call e.preventDefault() in your onSubmit handler to cancel that navigation.
How do I stop a React form from refreshing the page?
In your onSubmit handler, accept the event as the first argument (typed React.FormEvent in TypeScript) and call e.preventDefault() before doing anything else. That cancels the browser default and leaves React in control.
Why does the URL get a query string when I submit a React form?
That's the browser's default form submission: it serializes every named input field into a query string and navigates to that URL. e.preventDefault() cancels it. If you still see the query string, the handler isn't attached or preventDefault isn't being reached.
Keep learning
Learn it by doing. Open this in a live cloud workspace, make the change yourself, and keep a record of the work you can share.
Open the workspace →