How to fix Event handler received null instead of event object in React

ReactINTERMEDIATEMEDIUMUpdated Dec 9, 2025· 106 views

This error occurs when a React event handler tries to access properties of an event object that has been nullified, typically after an asynchronous operation. React's synthetic event pooling system (in React 16 and earlier) reuses event objects for performance, nullifying their properties after the handler completes. Understanding when and why this happens helps prevent unexpected null access errors.

Quick fix

Fixes Event handler received null instead of event object

// WRONG - event.target will be null inside setState
function handleChange(event) {
  setState((prevState) => ({
    ...prevState,
    username: event.target.value // ❌ null here
  }));
}

// CORRECT - extract value first
function handleChange(event) {
  const { value } = event.target; // ✅ Extract immediately
  setState((prevState) => ({
    ...prevState,
    username: value // ✅ Use the extracted value
  }));
}
  1. Extract event values immediately at the top of the handler
  2. Use event.persist() to keep the event object alive (React 16 only)
  3. Upgrade to React 17 or later to eliminate event pooling
  4. Fix debounced and throttled handlers
react — error
// WRONG - event.target will be null inside setState
function handleChange(event) {
setState((prevState) => ({
...prevState,
username: event.target.value // ❌ null here
}));
}
// CORRECT - extract value first
function handleChange(event) {
const { value } = event.target; // ✅ Extract immediately
setState((prevState) => ({
...prevState,
username: value // ✅ Use the extracted value
}));
}
Event handler received null instead of event object

What this error means

In React 16 and earlier, synthetic events are pooled and reused to improve performance. When an event handler finishes executing, React nullifies all properties of the synthetic event object so it can be reused for the next event. If you try to access event properties asynchronously (inside a setTimeout, Promise, setState callback, or any delayed operation), the event object will have already been nullified, causing all its properties to return null. This is particularly problematic when using event.target, event.currentTarget, or any other event property after the synchronous handler execution completes. React 17 and later removed event pooling, so this issue primarily affects older React versions, but legacy codebases may still encounter it.

Typical symptoms

  • Console errors showing "Cannot read property 'value' of null" when accessing event.target
  • Event properties like event.target, event.currentTarget, or event.type returning null inside async callbacks
  • Warning message "This synthetic event is reused for performance reasons" in React 16
  • Form input values not being captured correctly in setState callbacks
  • Event handler callbacks receiving null or undefined where an event object was expected
  • Debounced or throttled event handlers failing to access event properties
  • Event data appearing correct in synchronous code but null in setTimeout or Promise handlers

Common causes

  1. Accessing event properties inside a setState callback function without extracting the values first
  2. Using event objects inside setTimeout, setInterval, or other delayed asynchronous operations
  3. Passing the entire event object to debounced or throttled functions instead of extracting specific values
  4. Storing the event object in state or passing it to Promise chains without calling event.persist()
  5. Trying to access event properties after awaiting an async operation in the handler
  6. Using event objects inside callbacks passed to Array methods like map, filter, or forEach that execute later
  7. Accessing event properties in useEffect dependencies or other hooks that run after the handler completes

How to fix "Event handler received null instead of event object"

1Extract event values immediately at the top of the handler

The simplest and most recommended solution is to extract the specific values you need from the event object immediately when the handler executes, before any asynchronous operations. Store these values in variables and use them in your async code.

typescript
// WRONG - event.target will be null inside setState
function handleChange(event) {
  setState((prevState) => ({
    ...prevState,
    username: event.target.value // ❌ null here
  }));
}

// CORRECT - extract value first
function handleChange(event) {
  const { value } = event.target; // ✅ Extract immediately
  setState((prevState) => ({
    ...prevState,
    username: value // ✅ Use the extracted value
  }));
}
2Use event.persist() to keep the event object alive (React 16 only)

If you must keep the entire event object for async operations, call event.persist() at the beginning of your handler. This removes the synthetic event from the pool and prevents React from nullifying its properties. Note that this method is deprecated and does nothing in React 17+.

typescript
// For React 16 and earlier
function handleClick(event) {
  event.persist(); // ✅ Prevents nullification
  
  setTimeout(() => {
    console.log(event.target); // ✅ Works now
  }, 1000);
}

// For React 17+, event.persist() is not needed
function handleClick(event) {
  setTimeout(() => {
    console.log(event.target); // ✅ Works automatically
  }, 1000);
}
3Upgrade to React 17 or later to eliminate event pooling

React 17 removed the event pooling optimization entirely in modern browsers. If you're on React 16 or earlier and frequently encounter this issue, upgrading to React 17+ is the most permanent solution. Event objects will remain intact regardless of when you access them.

bash
# Check your current React version
npm list react

# Upgrade to React 17 or later
npm install react@latest react-dom@latest

# Or with yarn
yarn upgrade react@latest react-dom@latest

After upgrading, remove all event.persist() calls as they are no longer necessary.

4Fix debounced and throttled handlers

When using debounce or throttle utilities with event handlers, extract the values before passing them to the debounced function. Do not pass the entire event object.

typescript
import { debounce } from 'lodash';
import { useCallback } from 'react';

// WRONG - debounced function receives nullified event
const handleSearch = debounce((event) => {
  console.log(event.target.value); // ❌ null
}, 300);

// CORRECT - extract value first, then pass to debounced function
const debouncedSearch = useCallback(
  debounce((value: string) => {
    console.log(value); // ✅ Works
  }, 300),
  []
);

function handleSearch(event) {
  const { value } = event.target; // ✅ Extract immediately
  debouncedSearch(value); // ✅ Pass the value, not the event
}
5Handle async event handlers properly

If your event handler is async or uses Promises, extract event properties before any await statements or Promise chains.

typescript
// WRONG - event nullified after await
async function handleSubmit(event) {
  event.preventDefault();
  await someAsyncOperation();
  console.log(event.target); // ❌ null
}

// CORRECT - extract data before async operations
async function handleSubmit(event) {
  event.preventDefault();
  const formData = new FormData(event.target); // ✅ Extract first
  
  await someAsyncOperation();
  console.log(formData); // ✅ Works
}
6Review all event handlers for deferred access patterns

Search your codebase for patterns where event properties are accessed after the initial handler execution. Common patterns to look for include:

typescript
// Check for these patterns:
// 1. Events in setState callbacks
setState(() => ({ value: event.target.value }));

// 2. Events in setTimeout/setInterval
setTimeout(() => console.log(event.target), 100);

// 3. Events in Promise chains
promise.then(() => console.log(event.target));

// 4. Events stored in state or refs
setEventState(event); // Then used later

Replace these with immediate value extraction patterns shown in previous steps.

Advanced notes

The synthetic event pooling system was a performance optimization from React's early days when creating new objects was more expensive. In React 16, React would reuse a single SyntheticEvent instance for all events of the same type, nullifying properties between uses to prevent memory leaks and ensure predictable behavior. This pooling behavior only affected React 16 and earlier versions. In React 17, the React team removed event pooling for modern browsers because modern JavaScript engines handle object creation much more efficiently, and the complexity of managing pooled events outweighed the performance benefits. If you maintain a large legacy codebase on React 16, you may still need to handle this issue. A global search for event.persist() calls can help identify handlers that were previously affected. For TypeScript users, consider creating type-safe wrappers that extract event values and return properly typed data objects, preventing accidental event object usage in async contexts. Some third-party libraries that wrap React event handlers may also be affected, so check their documentation for React 17 compatibility if you encounter issues after upgrading.

Frequently asked questions

What does "Event handler received null instead of event object" mean?
In React 16 and earlier, synthetic events are pooled and reused to improve performance. When an event handler finishes executing, React nullifies all properties of the synthetic event object so it can be reused for the next event. If you try to access event properties asynchronously (inside a setTimeout, Promise, setState callback, or any delayed operation), the event object will have already been nullified, causing all its properties to return null.
How do I fix "Event handler received null instead of event object"?
This error occurs when a React event handler tries to access properties of an event object that has been nullified, typically after an asynchronous operation. React's synthetic event pooling system (in React 16 and earlier) reuses event objects for performance, nullifying their properties after the handler completes. Understanding when and why this happens helps prevent unexpected null access errors.
What causes "Event handler received null instead of event object"?
Accessing event properties inside a setState callback function without extracting the values first Using event objects inside setTimeout, setInterval, or other delayed asynchronous operations Passing the entire event object to debounced or throttled functions instead of extracting specific values
Is the "Event handler received null instead of event object" error serious?
It is a medium-severity error — more of a blocker than a risk. The steps above resolve it.

Official resources & further reading