From d659a4be361769e0fb769d35c02af3f315386416 Mon Sep 17 00:00:00 2001 From: JSap0914 Date: Mon, 22 Jun 2026 19:17:02 +0900 Subject: [PATCH 1/2] fix: honour explicit dependsOnOwnProps=false in getDependsOnOwnProps getDependsOnOwnProps used a truthy check on `mapToProps.dependsOnOwnProps`, so an explicitly-set `false` fell through to the length-based heuristic and was silently overridden. A 2-arg mapToProps with `.dependsOnOwnProps = false` was therefore treated as depending on ownProps, causing unnecessary re-renders whenever parent props changed. Fix: check `!= null` instead so that `false` (and `true`) are both respected as intentional user signals, while `undefined` / `null` still falls back to the arity heuristic. Adds four unit tests covering all combinations. --- src/connect/wrapMapToProps.ts | 2 +- test/connect/wrapMapToProps.spec.ts | 58 +++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 1 deletion(-) create mode 100644 test/connect/wrapMapToProps.spec.ts diff --git a/src/connect/wrapMapToProps.ts b/src/connect/wrapMapToProps.ts index c1d170328..0a95939be 100644 --- a/src/connect/wrapMapToProps.ts +++ b/src/connect/wrapMapToProps.ts @@ -48,7 +48,7 @@ export function wrapMapToPropsConstant( // therefore not reporting its length accurately.. // TODO Can this get pulled out so that we can subscribe directly to the store if we don't need ownProps? function getDependsOnOwnProps(mapToProps: MapToProps) { - return mapToProps.dependsOnOwnProps + return mapToProps.dependsOnOwnProps != null ? Boolean(mapToProps.dependsOnOwnProps) : mapToProps.length !== 1 } diff --git a/test/connect/wrapMapToProps.spec.ts b/test/connect/wrapMapToProps.spec.ts new file mode 100644 index 000000000..7f47bae76 --- /dev/null +++ b/test/connect/wrapMapToProps.spec.ts @@ -0,0 +1,58 @@ +import { wrapMapToPropsFunc } from '@internal/connect/wrapMapToProps' +import type { Dispatch } from 'redux' + +const fakeDispatch = (() => {}) as Dispatch +const fakeOptions = { displayName: 'TestComponent' } + +describe('wrapMapToProps', () => { + describe('getDependsOnOwnProps', () => { + it('infers dependsOnOwnProps=true from a 2-arity function with no explicit flag', () => { + const mapToProps = (_state: any, _ownProps: any) => ({}) + const proxy = wrapMapToPropsFunc(mapToProps, 'mapStateToProps')( + fakeDispatch, + fakeOptions, + ) + proxy({}, undefined) + expect(proxy.dependsOnOwnProps).toBe(true) + }) + + it('infers dependsOnOwnProps=false from a 1-arity function with no explicit flag', () => { + const mapToProps = (_state: any) => ({}) + const proxy = wrapMapToPropsFunc(mapToProps, 'mapStateToProps')( + fakeDispatch, + fakeOptions, + ) + proxy({}, undefined) + expect(proxy.dependsOnOwnProps).toBe(false) + }) + + it('respects an explicit dependsOnOwnProps=false even when function arity is 2', () => { + // This is the bug: a 2-arg function with dependsOnOwnProps=false set + // should have the explicit flag honoured, not be overridden by the + // length heuristic. + const mapToProps = (_state: any, _ownProps: any) => ({}) + ;(mapToProps as any).dependsOnOwnProps = false + + const proxy = wrapMapToPropsFunc(mapToProps, 'mapStateToProps')( + fakeDispatch, + fakeOptions, + ) + proxy({}, undefined) + // Before the fix: proxy.dependsOnOwnProps === true (bug – length heuristic wins) + // After the fix: proxy.dependsOnOwnProps === false (explicit flag is respected) + expect(proxy.dependsOnOwnProps).toBe(false) + }) + + it('respects an explicit dependsOnOwnProps=true even when function arity is 1', () => { + const mapToProps = (_state: any) => ({}) + ;(mapToProps as any).dependsOnOwnProps = true + + const proxy = wrapMapToPropsFunc(mapToProps, 'mapStateToProps')( + fakeDispatch, + fakeOptions, + ) + proxy({}, undefined) + expect(proxy.dependsOnOwnProps).toBe(true) + }) + }) +}) From 01878268d2ae980207607221644003ba9a479f92 Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Tue, 29 Sep 2026 22:06:04 -0400 Subject: [PATCH 2/2] Remove any casts from wrapMapToProps tests --- test/connect/wrapMapToProps.spec.ts | 60 ++++++++++++----------------- 1 file changed, 25 insertions(+), 35 deletions(-) diff --git a/test/connect/wrapMapToProps.spec.ts b/test/connect/wrapMapToProps.spec.ts index 7f47bae76..b646b4669 100644 --- a/test/connect/wrapMapToProps.spec.ts +++ b/test/connect/wrapMapToProps.spec.ts @@ -1,58 +1,48 @@ +import type { MapToProps } from '@internal/connect/wrapMapToProps' import { wrapMapToPropsFunc } from '@internal/connect/wrapMapToProps' import type { Dispatch } from 'redux' const fakeDispatch = (() => {}) as Dispatch const fakeOptions = { displayName: 'TestComponent' } +function getDependsOnOwnProps(mapToProps: MapToProps) { + const proxy = wrapMapToPropsFunc(mapToProps, 'mapStateToProps')( + fakeDispatch, + fakeOptions, + ) + proxy({}, undefined) + return proxy.dependsOnOwnProps +} + describe('wrapMapToProps', () => { describe('getDependsOnOwnProps', () => { it('infers dependsOnOwnProps=true from a 2-arity function with no explicit flag', () => { - const mapToProps = (_state: any, _ownProps: any) => ({}) - const proxy = wrapMapToPropsFunc(mapToProps, 'mapStateToProps')( - fakeDispatch, - fakeOptions, - ) - proxy({}, undefined) - expect(proxy.dependsOnOwnProps).toBe(true) + const mapToProps = (_state: unknown, _ownProps: unknown) => ({}) + + expect(getDependsOnOwnProps(mapToProps)).toBe(true) }) it('infers dependsOnOwnProps=false from a 1-arity function with no explicit flag', () => { - const mapToProps = (_state: any) => ({}) - const proxy = wrapMapToPropsFunc(mapToProps, 'mapStateToProps')( - fakeDispatch, - fakeOptions, - ) - proxy({}, undefined) - expect(proxy.dependsOnOwnProps).toBe(false) + const mapToProps = (_state: unknown) => ({}) + + expect(getDependsOnOwnProps(mapToProps)).toBe(false) }) it('respects an explicit dependsOnOwnProps=false even when function arity is 2', () => { - // This is the bug: a 2-arg function with dependsOnOwnProps=false set - // should have the explicit flag honoured, not be overridden by the - // length heuristic. - const mapToProps = (_state: any, _ownProps: any) => ({}) - ;(mapToProps as any).dependsOnOwnProps = false - - const proxy = wrapMapToPropsFunc(mapToProps, 'mapStateToProps')( - fakeDispatch, - fakeOptions, + const mapToProps = Object.assign( + (_state: unknown, _ownProps: unknown) => ({}), + { dependsOnOwnProps: false }, ) - proxy({}, undefined) - // Before the fix: proxy.dependsOnOwnProps === true (bug – length heuristic wins) - // After the fix: proxy.dependsOnOwnProps === false (explicit flag is respected) - expect(proxy.dependsOnOwnProps).toBe(false) + + expect(getDependsOnOwnProps(mapToProps)).toBe(false) }) it('respects an explicit dependsOnOwnProps=true even when function arity is 1', () => { - const mapToProps = (_state: any) => ({}) - ;(mapToProps as any).dependsOnOwnProps = true + const mapToProps = Object.assign((_state: unknown) => ({}), { + dependsOnOwnProps: true, + }) - const proxy = wrapMapToPropsFunc(mapToProps, 'mapStateToProps')( - fakeDispatch, - fakeOptions, - ) - proxy({}, undefined) - expect(proxy.dependsOnOwnProps).toBe(true) + expect(getDependsOnOwnProps(mapToProps)).toBe(true) }) }) })