How to fix preventDefault() not working on event in synthetic event handler in React

ReactINTERMEDIATEMEDIUMUpdated Dec 10, 2025· 123 views

React event handlers may fail to prevent default behavior when preventDefault() is called incorrectly or when accessing synthetic events asynchronously. This commonly occurs when functions are invoked instead of passed as references, or when trying to access event properties in async callbacks.

Quick fix

Fixes preventDefault() not working on event in synthetic event handler

// ❌ Wrong - calls the function immediately
<button onClick={handleClick()}>Click me</button>

// ✅ Correct - passes function reference
<button onClick={handleClick}>Click me</button>

// ✅ Also correct - arrow function with explicit event
<button onClick={(e) => handleClick(e)}>Click me</button>
  1. Pass event handler as a reference, not an invocation
  2. Ensure proper event parameter in handler function
  3. Use event.persist() for async operations in React < 17
  4. Extract event properties before async operations
react — error
// ❌ Wrong - calls the function immediately
<button onClick={handleClick()}>Click me</button>
// ✅ Correct - passes function reference
<button onClick={handleClick}>Click me</button>
// ✅ Also correct - arrow function with explicit event
<button onClick={(e) => handleClick(e)}>Click me</button>
preventDefault() not working on event in synthetic event handler

What this error means

This error occurs when React's synthetic event system prevents you from calling preventDefault() on an event object. React wraps native browser events in a SyntheticEvent object to provide cross-browser consistency. However, this wrapper introduces special behaviors that can cause preventDefault() to fail or become undefined. The most common scenario is when the event handler function is invoked immediately (with parentheses) instead of being passed as a reference, causing the event parameter to be undefined. Another frequent issue occurs in React versions before 17, where event pooling causes synthetic event properties to be nullified after the handler completes, making them inaccessible in asynchronous operations like setTimeout or async functions. In modern React (v17+), event pooling was removed, but developers still encounter issues when incorrectly passing or accessing event objects in their handlers.

Typical symptoms

  • Clicking a link or submitting a form triggers default browser behavior despite calling preventDefault()
  • Console error: "Cannot read property 'preventDefault' of undefined"
  • TypeError: e.preventDefault is not a function
  • preventDefault() works in synchronous code but fails in setTimeout or async callbacks
  • Event properties are null or undefined when accessed inside async operations
  • Form submissions reload the page even though preventDefault() is called

Common causes

  1. Event handler function is invoked with parentheses instead of passed as a reference (onClick={handleClick()} instead of onClick={handleClick})
  2. Incorrect this binding causing the event object to be undefined in class components
  3. Accessing the event object asynchronously in React < 17 without calling event.persist()
  4. Event parameter not properly passed through arrow functions or wrapper functions
  5. Calling preventDefault() after the event has been pooled and nullified (React < 17)
  6. Using the wrong event type or accessing properties that don't exist on the synthetic event

How to fix "preventDefault() not working on event in synthetic event handler"

1Pass event handler as a reference, not an invocation

The most common mistake is invoking the function in the JSX instead of passing it as a reference:

jsx
// ❌ Wrong - calls the function immediately
<button onClick={handleClick()}>Click me</button>

// ✅ Correct - passes function reference
<button onClick={handleClick}>Click me</button>

// ✅ Also correct - arrow function with explicit event
<button onClick={(e) => handleClick(e)}>Click me</button>

If you need to pass additional arguments, use an arrow function:

jsx
<button onClick={(e) => handleClick(e, additionalArg)}>
  Click me
</button>
2Ensure proper event parameter in handler function

Verify your event handler function accepts the event parameter:

jsx
// ✅ Correct function component
function MyComponent() {
  const handleSubmit = (e) => {
    e.preventDefault();
    console.log('Form submitted');
  };

  return <form onSubmit={handleSubmit}>...</form>;
}

// ✅ Correct class component with binding
class MyComponent extends React.Component {
  handleSubmit = (e) => {
    e.preventDefault();
    console.log('Form submitted');
  }

  render() {
    return <form onSubmit={this.handleSubmit}>...</form>;
  }
}

If using a class component with traditional methods, bind in the constructor:

jsx
class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.handleSubmit = this.handleSubmit.bind(this);
  }

  handleSubmit(e) {
    e.preventDefault();
    console.log('Form submitted');
  }

  render() {
    return <form onSubmit={this.handleSubmit}>...</form>;
  }
}
3Use event.persist() for async operations in React < 17

If you're using React 16 or earlier and need to access the event asynchronously, call event.persist():

jsx
// React < 17 only
const handleClick = (e) => {
  e.persist(); // Remove event from pool

  setTimeout(() => {
    console.log(e.type); // Now accessible
    e.preventDefault(); // Now works
  }, 100);
};

In React 17+, event pooling was removed, so this is no longer necessary. However, you still need to call preventDefault() synchronously:

jsx
// React 17+ - preventDefault must be called synchronously
const handleClick = (e) => {
  e.preventDefault(); // Call immediately

  setTimeout(() => {
    // Can access event properties, but preventDefault
    // must have been called before async operation
    console.log(e.type);
  }, 100);
};
4Extract event properties before async operations

For modern React, extract any needed event properties before entering async code:

jsx
const handleSubmit = (e) => {
  e.preventDefault(); // Call synchronously

  const formData = new FormData(e.target);
  const inputValue = e.target.elements.username.value;

  // Now safe to use in async operations
  setTimeout(() => {
    submitData(inputValue);
  }, 100);
};

// Or with async/await
const handleSubmit = async (e) => {
  e.preventDefault(); // Call before any await

  const data = {
    name: e.target.name.value,
    email: e.target.email.value
  };

  await fetch('/api/submit', {
    method: 'POST',
    body: JSON.stringify(data)
  });
};
5Verify event type and available methods

Ensure you're using the correct event type for your handler:

jsx
// Form submission events
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
  e.preventDefault();
};

// Click events
const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
  e.preventDefault();
};

// Keyboard events
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
  if (e.key === 'Enter') {
    e.preventDefault();
  }
};

Check that the event type supports preventDefault(). Most UI events do, but some custom events might not.

Advanced notes

Event Pooling History: React's event pooling was a performance optimization in React 16 and earlier, where SyntheticEvent objects were reused across different events. This caused properties to be nullified after the handler completed, which confused many developers when trying to access events asynchronously. React 17 removed this behavior, making SyntheticEvents behave more like native browser events.

Native Events vs Synthetic Events: React's SyntheticEvent implements the same interface as native browser events, including stopPropagation() and preventDefault(). However, if you need access to the underlying native event, use event.nativeEvent.

Return false doesn't work: Unlike jQuery or vanilla JavaScript, returning false from a React event handler does NOT prevent default behavior. You must explicitly call e.preventDefault().

Performance considerations: In React 17+, while event pooling is removed, React still uses event delegation by attaching handlers to the document root (or root container in React 17+). This means your event handlers receive SyntheticEvents, not native events.

TypeScript best practices: Always type your event handlers properly to catch preventDefault() issues at compile time. Use React.FormEvent, React.MouseEvent, React.KeyboardEvent, etc., with the appropriate generic type for the element.

Server Components: In React Server Components (Next.js 13+ App Router), event handlers cannot be used directly in server components. You must mark components with "use client" if they need interactive event handlers.

Frequently asked questions

What does "preventDefault() not working on event in synthetic event handler" mean?
This error occurs when React's synthetic event system prevents you from calling preventDefault() on an event object. React wraps native browser events in a SyntheticEvent object to provide cross-browser consistency. However, this wrapper introduces special behaviors that can cause preventDefault() to fail or become undefined.
How do I fix "preventDefault() not working on event in synthetic event handler"?
React event handlers may fail to prevent default behavior when preventDefault() is called incorrectly or when accessing synthetic events asynchronously. This commonly occurs when functions are invoked instead of passed as references, or when trying to access event properties in async callbacks.
What causes "preventDefault() not working on event in synthetic event handler"?
Event handler function is invoked with parentheses instead of passed as a reference (onClick={handleClick()} instead of onClick={handleClick}) Incorrect this binding causing the event object to be undefined in class components Accessing the event object asynchronously in React < 17 without calling event.persist()
Is the "preventDefault() not working on event in synthetic event handler" error serious?
It is a medium-severity error — more of a blocker than a risk. The steps above resolve it.

Official resources & further reading