Skip to content

Commit 3c9766e

Browse files
authored
Merge pull request #1958 from reduxjs/bugfix/connect-context-prop
2 parents 4fcd42c + 1236861 commit 3c9766e

File tree

3 files changed

+65
-20
lines changed

3 files changed

+65
-20
lines changed

src/components/connect.tsx

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11
/* eslint-disable valid-jsdoc, @typescript-eslint/no-unused-vars */
22
import hoistStatics from 'hoist-non-react-statics'
3-
import React, { useContext, useMemo, useRef } from 'react'
3+
import React, { ComponentType, useContext, useMemo, useRef } from 'react'
44
import { isValidElementType, isContextConsumer } from 'react-is'
55

66
import type { Store } from 'redux'
77

88
import type {
9-
AdvancedComponentDecorator,
109
ConnectedComponent,
1110
InferableComponentEnhancer,
1211
InferableComponentEnhancerWithProps,
1312
ResolveThunks,
1413
DispatchProp,
14+
ConnectPropsMaybeWithoutContext,
1515
} from '../types'
1616

1717
import defaultSelectorFactory, {
@@ -470,18 +470,18 @@ function connect<
470470

471471
const Context = context
472472

473-
type WrappedComponentProps = TOwnProps & ConnectProps
474-
475473
const initMapStateToProps = mapStateToPropsFactory(mapStateToProps)
476474
const initMapDispatchToProps = mapDispatchToPropsFactory(mapDispatchToProps)
477475
const initMergeProps = mergePropsFactory(mergeProps)
478476

479477
const shouldHandleStateChanges = Boolean(mapStateToProps)
480478

481-
const wrapWithConnect: AdvancedComponentDecorator<
482-
TOwnProps,
483-
WrappedComponentProps
484-
> = (WrappedComponent) => {
479+
const wrapWithConnect = <TProps,>(
480+
WrappedComponent: ComponentType<TProps>
481+
) => {
482+
type WrappedComponentProps = TProps &
483+
ConnectPropsMaybeWithoutContext<TProps>
484+
485485
if (
486486
process.env.NODE_ENV !== 'production' &&
487487
!isValidElementType(WrappedComponent)

src/types.ts

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,6 @@ export interface DispatchProp<A extends Action = AnyAction> {
2727
dispatch: Dispatch<A>
2828
}
2929

30-
export type AdvancedComponentDecorator<TProps, TOwnProps> = (
31-
component: ComponentType<TProps>
32-
) => ComponentType<TOwnProps>
33-
3430
/**
3531
* A property P will be present if:
3632
* - it is present in DecorationTargetProps
@@ -94,22 +90,34 @@ export type ConnectedComponent<
9490
WrappedComponent: C
9591
}
9692

93+
export type ConnectPropsMaybeWithoutContext<TActualOwnProps> =
94+
TActualOwnProps extends { context: any }
95+
? Omit<ConnectProps, 'context'>
96+
: ConnectProps
97+
98+
type Identity<T> = T
99+
export type Mapped<T> = Identity<{ [k in keyof T]: T[k] }>
100+
97101
// Injects props and removes them from the prop requirements.
98102
// Will not pass through the injected props if they are passed in during
99103
// render. Also adds new prop requirements from TNeedsProps.
100-
// Uses distributive omit to preserve discriminated unions part of original prop type
104+
// Uses distributive omit to preserve discriminated unions part of original prop type.
105+
// Note> Most of the time TNeedsProps is empty, because the overloads in `Connect`
106+
// just pass in `{}`. The real props we need come from the component.
101107
export type InferableComponentEnhancerWithProps<TInjectedProps, TNeedsProps> = <
102108
C extends ComponentType<Matching<TInjectedProps, GetProps<C>>>
103109
>(
104110
component: C
105111
) => ConnectedComponent<
106112
C,
107-
DistributiveOmit<
108-
GetLibraryManagedProps<C>,
109-
keyof Shared<TInjectedProps, GetLibraryManagedProps<C>>
110-
> &
111-
TNeedsProps &
112-
ConnectProps
113+
Mapped<
114+
DistributiveOmit<
115+
GetLibraryManagedProps<C>,
116+
keyof Shared<TInjectedProps, GetLibraryManagedProps<C>>
117+
> &
118+
TNeedsProps &
119+
ConnectPropsMaybeWithoutContext<TNeedsProps & GetProps<C>>
120+
>
113121
>
114122

115123
// Injects props and removes them from the prop requirements.

test/typetests/connect-options-and-issues.tsx

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import {
3232
createStoreHook,
3333
TypedUseSelectorHook,
3434
} from '../../src/index'
35+
import { ConnectPropsMaybeWithoutContext } from '../../src/types'
3536

3637
import { expectType } from '../typeTestHelpers'
3738

@@ -464,7 +465,7 @@ function TestOptionalPropsMergedCorrectly() {
464465
}
465466
}
466467

467-
connect(mapStateToProps, mapDispatchToProps)(Component)
468+
const Connected = connect(mapStateToProps, mapDispatchToProps)(Component)
468469
}
469470

470471
function TestMoreGeneralDecorationProps() {
@@ -881,3 +882,39 @@ function testPreserveDiscriminatedUnions() {
881882
;<ConnectedMyText type="localized" color="red" />
882883
;<ConnectedMyText type="localized" color="red" params={someParams} />
883884
}
885+
886+
function issue1187ConnectAcceptsPropNamedContext() {
887+
const mapStateToProps = (state: { name: string }) => {
888+
return {
889+
name: state.name,
890+
}
891+
}
892+
893+
const connector = connect(mapStateToProps)
894+
895+
type PropsFromRedux = ConnectedProps<typeof connector>
896+
897+
interface IButtonOwnProps {
898+
label: string
899+
context: 'LIST' | 'CARD'
900+
}
901+
type IButtonProps = IButtonOwnProps & PropsFromRedux
902+
903+
function Button(props: IButtonProps) {
904+
const { name, label, context } = props
905+
return (
906+
<button>
907+
{name} - {label} - {context}
908+
</button>
909+
)
910+
}
911+
912+
const ConnectedButton = connector(Button)
913+
914+
// Since `IButtonOwnProps` includes a field named `context`, the final
915+
// connected component _should_ use exactly that type, and omit the
916+
// built-in `context: ReactReduxContext` field definition.
917+
// If the types are broken, then `context` will have an error like:
918+
// Type '"LIST"' is not assignable to type '("LIST" | "CARD") & (Context<ReactReduxContextValue<any, AnyAction>> | undefined)'
919+
return <ConnectedButton label="a" context="LIST" />
920+
}

0 commit comments

Comments
 (0)