Open Source
React Redux

Published: August 15, 2026 · by Srinu Desetti · reduxjs/react-redux#2306 (opens in a new tab)

Better mapStateToProps Error Reporting in React Redux

React Redux is the official React binding for Redux, used by millions of apps. My contribution (issue #1942 (opens in a new tab)) is a developer-experience improvement in the connect() API: when mapStateToProps throws during a store update, developers now immediately see what failed and which component caused it.

An important framing up front:

I did not change Redux itself. I changed the error-reporting behavior inside React Redux's older connect() API. The existing error-handling behavior was fully preserved.

Background: connect() vs Hooks

Modern apps use Hooks (useSelector, useDispatch). Before Hooks, apps used:

Counter.jsx
const mapStateToProps = state => ({
  count: state.counter.value
})

export default connect(mapStateToProps)(Counter)

connect() is still supported because many enterprise applications rely on it. My PR targets this path.

When Redux state changes, the simplified flow is:

dispatch()

Redux Store state changes

React-Redux subscription

subscribeUpdates()

childPropsSelector()

mapStateToProps()

new props → component updates

The Problem (Issue #1942)

Imagine a developer writes:

const mapStateToProps = state => ({
  name: state.user.profile.name
})

But the actual state is { user: null }. Then state.user.profile throws Cannot read properties of nullduring a store subscription update, deep inside React Redux.

The existing code already caught the error:

src/components/connect.tsx — existing catch
try {
  newChildProps = childPropsSelector(latestStoreState, wrapperProps.current)
} catch (e) {
  error = e
  lastThrownError = e
}

But there was no immediate log. The error was silently stored and thrown later:

mapStateToProps() → ERROR → catch(e) → save the error
    → no useful immediate log
    → ...time passes...
    → error gets thrown later, with less context

In an app with dozens of connected components — Connect(User), Connect(Product), Connect(Cart)… — the developer had no idea which one broke.

What I Added

A development-only console.error inside the existing catch block:

src/components/connect.tsx — my change
if (process.env.NODE_ENV !== 'production') {
  console.error(
    `An error occurred in \`mapStateToProps\` (or a selector) of the \`connect()\`-ed component "${displayName}". ` +
      'See https://github.com/reduxjs/react-redux/issues/1942 for details.',
    e,
  )
}

Three deliberate decisions in those few lines:

1. Pass the Error object, not just the message

console.error("message", e)   // ✅ DevTools shows the real Error + stack trace
console.error(e.message)      // ❌ loses the stack

Passing e separately lets browser DevTools display the actual Error object — message, stack, and source location.

2. Include the component's displayName

subscribeUpdates() didn't have the component name — but _connect() did. I threaded it through as a new argument:

src/components/connect.tsx — threading displayName
// before
subscribeUpdates(..., additionalSubscribeListener)

// after
subscribeUpdates(..., additionalSubscribeListener, displayName)

So instead of a generic "an error occurred in mapStateToProps", the developer sees:

An error occurred in `mapStateToProps` of the
`connect()`-ed component "Connect(Product)".

They know exactly where to look.

3. Development-only

if (process.env.NODE_ENV !== 'production')

Bundlers replace process.env.NODE_ENV at build time, so in production builds this becomes if (false) { … } and the optimizer removes the code entirely — zero production overhead.

The Test I Added

The test uses a mapStateToProps that throws on demand, spies on console.error with vi.spyOn, and asserts two things after a dispatch:

Actual Error object logged      ✅ (message === "mapStateToProps failed")
Component name in the message   ✅ ("Connect(Container)")

A second, successful dispatch then clears the stored error state so the component unmounts cleanly.

Scope — What This Does and Doesn't Cover

The deciding factor is connect() vs Hooks, not class vs function components.

PatternCovered by my change?
Class component + connect()✅ Yes
Function component + connect()✅ Yes
useSelector() / useDispatch()❌ No — different code path

I did not change: reducers, actions, dispatch(), the store, useSelector(), React rendering, or the existing error propagation. Only the error reporting inside connect().

Impact

  • Debugging failures in connected components went from "search the whole app" to "read the console line" — the message names the exact component.
  • The real Error object with its stack trace lands in DevTools at the moment of failure, not later.
  • Zero cost in production builds; zero behavior change for existing error handling.

What I Contributed

  • Identified the pain point from issue #1942.
  • Implemented the development-only logging with the component displayName.
  • Threaded displayName from _connect() into subscribeUpdates().
  • Added test coverage proving both the Error object and component name are logged.
  • Collaborated with the Redux maintainers until the PR was merged.

View the pull request → reduxjs/react-redux#2306 (opens in a new tab)


← Previous: Axios circular reference fix · Next: Nodemailer filename escaping →


Frequently Asked Questions

Does this change affect Redux itself?

No. Reducers, actions, dispatch, and the store are untouched. The change lives in React Redux's connect() implementation — specifically the error reporting inside subscribeUpdates().

Does it work with useSelector()?

No. useSelector() does not go through the connect() + mapStateToProps path this change modifies. It applies only to components wrapped with connect() — both class and function components.

Why is the logging development-only?

It is a developer debugging aid. The process.env.NODE_ENV !== 'production' guard lets bundlers strip the code from production builds entirely, so there is zero runtime cost for end users.

Why pass the Error object to console.error instead of just the message?

Passing the Error object separately lets browser DevTools render the full error — message, stack trace, and source location — instead of a flat string, which makes locating the failing selector much faster.