Skip to content

fix(connect): split mapStateToProps/mapDispatchToProps unions into ordered overloads (#2244)#2340

Open
veksa wants to merge 1 commit into
reduxjs:masterfrom
veksa:fix/connect-mapstate-overload-ordering
Open

fix(connect): split mapStateToProps/mapDispatchToProps unions into ordered overloads (#2244)#2340
veksa wants to merge 1 commit into
reduxjs:masterfrom
veksa:fix/connect-mapstate-overload-ordering

Conversation

@veksa

@veksa veksa commented Jul 20, 2026

Copy link
Copy Markdown

Fixes #2244.

connect's mapStateToProps and mapDispatchToProps are typed as unions that include both a "factory" form (() => (state) => props) and a plain form. A factory is structurally assignable to its plain counterpart, so the compiler has to pick a "first" candidate to infer TStateProps / TDispatchProps from — and tsc and the new native compiler (tsgo / TS 7) order union members differently. One of them ends up inferring the wrong candidate and the connected component's props collapse to {} (a required prop shows up as "missing" at the call site).

As Anders suggested in the upstream analysis (microsoft/typescript-go#977), the robust fix is to break the union into separate overloads listed in order of preference rather than relying on union ordering. So I've split the affected overloads with the factory form listed first, followed by the plain form. A plain object-returning function isn't assignable to the factory signature, so a factory matches the first overload and everything else falls through — deterministically, and independent of the compiler.

A couple of notes on scope:

  • The object form of mapDispatchToProps doesn't get its own factory overload: factories and dispatch functions are already matched by the function-form overloads that precede it, so the object overloads only need the plain (MapDispatchToProps) parameter. This keeps the overload count from exploding.
  • The mergeProps overloads deliberately keep the union parameters. There mergeProps receives stateProps / dispatchProps as contextually-typed (non-inferring) parameters, so those types can only come from the map functions — and a leading factory overload would swallow a plain map function and collapse the inferred props to {}. Keeping both forms as candidates in a single overload preserves the original behavior. It's documented inline.

Types only, no runtime changes.

I added type tests covering the repro cases from the issue (plain/factory mapStateToProps, the function/factory/object forms of mapDispatchToProps, a factory + factory combination, and the wrong-return-type cases). They pass under both tsc and the native compiler, and — this is the important part — they stay green even if the union members are reordered, which is the actual failure mode here: reordering the unions on plain tsc reproduces the exact errors from the report before this change, and no longer does after it.

One caveat worth mentioning: current tsgo dev builds may already infer these cases correctly, but the union ordering is still load-bearing and fragile, and the compiler-side issue was closed as "not planned" — so this makes the types stable regardless of compiler or union order.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Typescript 7 won’t infer container types correctly

1 participant