How to Use preventDefault in React to Stop Link Navigation and Page Refresh
Call event.preventDefault() inside your onClick handler to stop a link's default browser navigation while preserving the href attribute for accessibility and SEO.
React's SyntheticEvent system normalizes browser events across environments, providing a consistent preventDefault() method identical to the native DOM API. When building single-page applications with the facebook/react repository, preventing default link behavior is essential for client-side routing without full page refreshes.
Understanding preventDefault in React's SyntheticEvent System
React implements event normalization in packages/react-dom-bindings/src/events/SyntheticEvent.js, where the preventDefault() method sets an internal defaultPrevented flag and forwards the call to the underlying native event when available. This architecture ensures cross-browser consistency while maintaining the standard DOM API that developers expect.
The test suite in packages/react-dom/src/events/__tests__/SyntheticEvent-test.js verifies that calling preventDefault() correctly marks the event as canceled, which React's event system respects during the bubbling phase.
The Correct Pattern for preventDefault in React Links
To prevent a link from causing a full-page refresh while maintaining accessibility and SEO benefits, follow this three-step pattern:
- Attach an
onClickhandler to the<a>element. - Call
event.preventDefault()on the synthetic event passed to the handler. - Execute client-side navigation using your routing solution or state updates.
function SmoothLink() {
const handleClick = (e) => {
e.preventDefault(); // Stops the browser's default navigation
console.log('Navigation prevented, executing custom logic');
};
return (
<a href="/target-page" onClick={handleClick}>
Click without refreshing
</a>
);
}
Practical Implementations of preventDefault in React
Basic Link That Stays on Page
For links that trigger actions rather than navigation, prevent the default behavior and handle the interaction programmatically:
function StayOnPageLink() {
const handleClick = (e) => {
e.preventDefault(); // Stops page reload
console.log('Link clicked, but no navigation');
};
return (
<a href="/unused" onClick={handleClick}>
Click me – stay on page
</a>
);
}
Client-Side Navigation with React Router
When using React Router v6, combine preventDefault with the useNavigate hook to enable smooth SPA transitions without losing the href attribute for accessibility:
import { useNavigate } from 'react-router-dom';
function NavLink() {
const navigate = useNavigate();
const onClick = (e) => {
e.preventDefault(); // Cancel native navigation
navigate('/dashboard'); // Smooth client-side transition
};
return (
<a href="/dashboard" onClick={onClick}>
Dashboard
</a>
);
}
Preventing Form Submission via Link
Links inside forms can inadvertently trigger submissions. Use preventDefault to handle the interaction without submitting the form, as demonstrated in packages/react-dom/src/__tests__/ReactDOMForm-test.js:
function FormWithLink() {
const onLinkClick = (e) => {
e.preventDefault(); // Stop form submission
// Custom handling logic here
};
return (
<form action="/submit">
<input type="text" name="name" />
<a href="/submit" onClick={onLinkClick}>Submit via link</a>
</form>
);
}
Using Buttons Instead of Links
When no actual navigation occurs, a <button type="button"> eliminates the need for preventDefault entirely and provides better semantic markup:
function ActionButton() {
const onClick = () => {
console.log('Button clicked');
};
return <button type="button" onClick={onClick}>Do something</button>;
}
How React's SyntheticEvent Handles preventDefault Internally
According to the facebook/react source code, the preventDefault implementation resides in packages/react-dom-bindings/src/events/SyntheticEvent.js. When invoked, it sets the internal defaultPrevented property to true and proxies the call to the underlying native browser event if one exists.
The test suite in packages/react-dom/src/events/__tests__/SyntheticEvent-test.js validates this behavior, ensuring that isDefaultPrevented() returns true after the method is called. Additionally, packages/react-dom/src/__tests__/ReactDOMForm-test.js contains multiple assertions confirming that preventDefault successfully stops form submissions, demonstrating its reliability across different event types.
Summary
- React's SyntheticEvent system provides a normalized
preventDefault()method that works consistently across browsers. - Always call
event.preventDefault()inside youronClickhandler to stop link navigation without removing thehrefattribute. - Keep valid
hrefvalues for accessibility and SEO, even when handling navigation client-side. - For React Router applications, combine
preventDefaultwithuseNavigatefor smooth SPA transitions. - Consider using
<button type="button">instead of<a>tags when no actual navigation occurs, eliminating the need forpreventDefault.
Frequently Asked Questions
What is the difference between preventDefault and stopPropagation in React?
preventDefault stops the browser's default action for an element, such as following a link or submitting a form, without preventing the event from bubbling up to parent elements. stopPropagation stops the event from bubbling further up the DOM tree but does not prevent the default browser action. In React links, you typically need preventDefault to stop navigation, while stopPropagation is only necessary if you want to prevent parent click handlers from firing.
Why does my page still refresh when I use preventDefault in React?
If the page still refreshes after calling preventDefault, ensure you are calling it on the correct event object passed to your handler, not a stale or cached version. Also verify that your handler is actually being invoked—common mistakes include forgetting to attach the onClick prop to the element or having an earlier handler call event.stopPropagation() which prevents your handler from running. According to the React source in packages/react-dom-bindings/src/events/SyntheticEvent.js, preventDefault must be called before the event handler returns for the flag to take effect.
Should I use a button or a link when implementing preventDefault in React?
Use an <a> tag when the action represents navigation to another resource, even if handled client-side, because it provides proper semantics for screen readers, keyboard accessibility, and SEO crawling. Use a <button type="button"> when triggering an action on the current page, such as opening a modal or submitting data via API, because buttons have correct semantic meaning for actions rather than navigation, and they do not require preventDefault to prevent page refreshes.
How do I test preventDefault behavior in React components?
Test preventDefault by spying on the event object passed to your handler and asserting that the method was called. In React Testing Library, you can verify that navigation did not occur by checking that window.location remains unchanged or that your navigation mock, such as useNavigate from React Router, was called instead of a full page reload. The React test suite in packages/react-dom/src/events/__tests__/SyntheticEvent-test.js demonstrates asserting isDefaultPrevented() to verify the internal state change after calling preventDefault.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →