, P> = ComponentType & NonReactStatics & {
WrappedComponent: C;
};
+// @public
+export type ConnectedProps = TConnector extends InferableComponentEnhancerWithProps ? unknown extends TInjectedProps ? TConnector extends InferableComponentEnhancer ? TInjectedProps : never : TInjectedProps : never;
+
// @public (undocumented)
export interface ConnectProps {
// (undocumented)
diff --git a/package.json b/package.json
index 19ee78f8f..ae298c88c 100644
--- a/package.json
+++ b/package.json
@@ -30,12 +30,13 @@
"build:types": "tsc",
"build": "yarn build:types && yarn build:commonjs && yarn build:es && yarn build:umd && yarn build:umd:min",
"clean": "rimraf lib dist es coverage",
- "api-types": "api-extractor --local",
+ "api-types": "api-extractor run --local",
"format": "prettier --write \"{src,test}/**/*.{js,ts}\" \"docs/**/*.md\"",
"lint": "eslint src --ext ts,js test/utils test/components test/hooks",
"prepare": "yarn clean && yarn build",
"pretest": "yarn lint",
"test": "jest",
+ "type-tests": "yarn tsc -p test/typetests",
"coverage": "codecov"
},
"workspaces": [
@@ -54,7 +55,6 @@
},
"dependencies": {
"@babel/runtime": "^7.12.1",
- "@types/react-redux": "^7.1.16",
"hoist-non-react-statics": "^3.3.2",
"loose-envify": "^1.4.0",
"prop-types": "^15.7.2",
@@ -80,6 +80,11 @@
"@testing-library/react": "^12.0.0",
"@testing-library/react-hooks": "^3.4.2",
"@testing-library/react-native": "^7.1.0",
+ "@types/object-assign": "^4.0.30",
+ "@types/react": "^17.0.14",
+ "@types/react-dom": "^17.0.9",
+ "@types/react-is": "^17.0.1",
+ "@types/react-redux": "^7.1.18",
"@typescript-eslint/eslint-plugin": "^4.28.0",
"@typescript-eslint/parser": "^4.28.0",
"babel-eslint": "^10.1.0",
diff --git a/src/components/connectAdvanced.tsx b/src/components/connectAdvanced.tsx
index 56bcd65e2..77e433814 100644
--- a/src/components/connectAdvanced.tsx
+++ b/src/components/connectAdvanced.tsx
@@ -1,16 +1,11 @@
import hoistStatics from 'hoist-non-react-statics'
-import React, {
- useContext,
- useMemo,
- useRef,
- useReducer,
- useLayoutEffect,
-} from 'react'
+import React, { useContext, useMemo, useRef, useReducer } from 'react'
import { isValidElementType, isContextConsumer } from 'react-is'
import type { Store } from 'redux'
import type { SelectorFactory } from '../connect/selectorFactory'
import { createSubscription, Subscription } from '../utils/Subscription'
import { useIsomorphicLayoutEffect } from '../utils/useIsomorphicLayoutEffect'
+import type { AdvancedComponentDecorator, ConnectedComponent } from '../types'
import {
ReactReduxContext,
@@ -31,7 +26,7 @@ const stringifyComponent = (Comp: unknown) => {
}
function storeStateUpdatesReducer(
- state: [payload: unknown, counter: number],
+ state: [unknown, number],
action: { payload: unknown }
) {
const [, updateCount] = state
@@ -108,7 +103,7 @@ function subscribeUpdates(
)
} catch (e) {
error = e
- lastThrownError = e
+ lastThrownError = e as Error | null
}
if (!error) {
@@ -182,16 +177,7 @@ export interface ConnectAdvancedOptions {
pure?: boolean
}
-interface AnyObject {
- [x: string]: any
-}
-
-export default function connectAdvanced<
- S,
- TProps,
- TOwnProps,
- TFactoryOptions extends AnyObject = {}
->(
+function connectAdvanced(
/*
selectorFactory is a func that is responsible for returning the selector function used to
compute new props from state, props, and dispatch. For example:
@@ -235,9 +221,19 @@ export default function connectAdvanced<
) {
const Context = context
- return function wrapWithConnect(
- WrappedComponent: WC
- ) {
+ type WrappedComponentProps = TOwnProps & ConnectProps
+
+ /*
+ return function wrapWithConnect<
+ WC extends React.ComponentType<
+ Matching, GetProps>
+ >
+ >(WrappedComponent: WC) {
+ */
+ const wrapWithConnect: AdvancedComponentDecorator<
+ TProps,
+ WrappedComponentProps
+ > = (WrappedComponent) => {
if (
process.env.NODE_ENV !== 'production' &&
!isValidElementType(WrappedComponent)
@@ -483,7 +479,14 @@ export default function connectAdvanced<
// If we're in "pure" mode, ensure our wrapper component only re-renders when incoming props have changed.
const _Connect = pure ? React.memo(ConnectFunction) : ConnectFunction
- const Connect = _Connect as typeof _Connect & { WrappedComponent: WC }
+ type ConnectedWrapperComponent = typeof _Connect & {
+ WrappedComponent: typeof WrappedComponent
+ }
+
+ const Connect = _Connect as ConnectedComponent<
+ typeof WrappedComponent,
+ WrappedComponentProps
+ >
Connect.WrappedComponent = WrappedComponent
Connect.displayName = ConnectFunction.displayName = displayName
@@ -492,12 +495,11 @@ export default function connectAdvanced<
props,
ref
) {
+ // @ts-ignore
return
})
- const forwarded = _forwarded as typeof _forwarded & {
- WrappedComponent: WC
- }
+ const forwarded = _forwarded as ConnectedWrapperComponent
forwarded.displayName = displayName
forwarded.WrappedComponent = WrappedComponent
return hoistStatics(forwarded, WrappedComponent)
@@ -505,4 +507,8 @@ export default function connectAdvanced<
return hoistStatics(Connect, WrappedComponent)
}
+
+ return wrapWithConnect
}
+
+export default connectAdvanced
diff --git a/src/connect/connect.ts b/src/connect/connect.ts
index d83513b2f..ae95fe28b 100644
--- a/src/connect/connect.ts
+++ b/src/connect/connect.ts
@@ -1,4 +1,5 @@
-import type { Dispatch } from 'redux'
+/* eslint-disable valid-jsdoc, @typescript-eslint/no-unused-vars */
+import type { Dispatch, Action, AnyAction } from 'redux'
import connectAdvanced from '../components/connectAdvanced'
import type { ConnectAdvancedOptions } from '../components/connectAdvanced'
import shallowEqual from '../utils/shallowEqual'
@@ -9,8 +10,16 @@ import defaultSelectorFactory, {
MapStateToPropsParam,
MapDispatchToPropsParam,
MergeProps,
+ MapDispatchToPropsNonObject,
+ SelectorFactory,
} from './selectorFactory'
-import type { DefaultRootState } from '../types'
+import type {
+ DefaultRootState,
+ InferableComponentEnhancer,
+ InferableComponentEnhancerWithProps,
+ ResolveThunks,
+ DispatchProp,
+} from '../types'
/*
connect is a facade over connectAdvanced. It turns its args into a compatible
@@ -52,6 +61,21 @@ function strictEqual(a: unknown, b: unknown) {
return a === b
}
+/**
+ * Infers the type of props that a connector will inject into a component.
+ */
+export type ConnectedProps =
+ TConnector extends InferableComponentEnhancerWithProps<
+ infer TInjectedProps,
+ any
+ >
+ ? unknown extends TInjectedProps
+ ? TConnector extends InferableComponentEnhancer
+ ? TInjectedProps
+ : never
+ : TInjectedProps
+ : never
+
export interface ConnectOptions<
State = DefaultRootState,
TStateProps = {},
@@ -77,6 +101,133 @@ export interface ConnectOptions<
forwardRef?: boolean | undefined
}
+/*
+export interface Connect {
+ // tslint:disable:no-unnecessary-generics
+ (): InferableComponentEnhancer
+
+ (
+ mapStateToProps: MapStateToPropsParam
+ ): InferableComponentEnhancerWithProps
+
+ (
+ mapStateToProps: null | undefined,
+ mapDispatchToProps: MapDispatchToPropsNonObject
+ ): InferableComponentEnhancerWithProps
+
+ (
+ mapStateToProps: null | undefined,
+ mapDispatchToProps: MapDispatchToPropsParam
+ ): InferableComponentEnhancerWithProps<
+ ResolveThunks,
+ TOwnProps
+ >
+
+ (
+ mapStateToProps: MapStateToPropsParam,
+ mapDispatchToProps: MapDispatchToPropsNonObject
+ ): InferableComponentEnhancerWithProps<
+ TStateProps & TDispatchProps,
+ TOwnProps
+ >
+
+ (
+ mapStateToProps: MapStateToPropsParam,
+ mapDispatchToProps: MapDispatchToPropsParam
+ ): InferableComponentEnhancerWithProps<
+ TStateProps & ResolveThunks,
+ TOwnProps
+ >
+
+ (
+ mapStateToProps: null | undefined,
+ mapDispatchToProps: null | undefined,
+ mergeProps: MergeProps
+ ): InferableComponentEnhancerWithProps
+
+ <
+ TStateProps = {},
+ no_dispatch = {},
+ TOwnProps = {},
+ TMergedProps = {},
+ State = DefaultState
+ >(
+ mapStateToProps: MapStateToPropsParam,
+ mapDispatchToProps: null | undefined,
+ mergeProps: MergeProps
+ ): InferableComponentEnhancerWithProps
+
+ (
+ mapStateToProps: null | undefined,
+ mapDispatchToProps: MapDispatchToPropsParam,
+ mergeProps: MergeProps
+ ): InferableComponentEnhancerWithProps
+
+ (
+ mapStateToProps: MapStateToPropsParam,
+ mapDispatchToProps: null | undefined,
+ mergeProps: null | undefined,
+ options: ConnectOptions
+ ): InferableComponentEnhancerWithProps
+
+ (
+ mapStateToProps: null | undefined,
+ mapDispatchToProps: MapDispatchToPropsNonObject,
+ mergeProps: null | undefined,
+ options: ConnectOptions<{}, TStateProps, TOwnProps>
+ ): InferableComponentEnhancerWithProps
+
+ (
+ mapStateToProps: null | undefined,
+ mapDispatchToProps: MapDispatchToPropsParam,
+ mergeProps: null | undefined,
+ options: ConnectOptions<{}, TStateProps, TOwnProps>
+ ): InferableComponentEnhancerWithProps<
+ ResolveThunks,
+ TOwnProps
+ >
+
+ (
+ mapStateToProps: MapStateToPropsParam,
+ mapDispatchToProps: MapDispatchToPropsNonObject,
+ mergeProps: null | undefined,
+ options: ConnectOptions
+ ): InferableComponentEnhancerWithProps<
+ TStateProps & TDispatchProps,
+ TOwnProps
+ >
+
+ (
+ mapStateToProps: MapStateToPropsParam,
+ mapDispatchToProps: MapDispatchToPropsParam,
+ mergeProps: null | undefined,
+ options: ConnectOptions
+ ): InferableComponentEnhancerWithProps<
+ TStateProps & ResolveThunks,
+ TOwnProps
+ >
+
+ <
+ TStateProps = {},
+ TDispatchProps = {},
+ TOwnProps = {},
+ TMergedProps = {},
+ State = DefaultState
+ >(
+ mapStateToProps: MapStateToPropsParam,
+ mapDispatchToProps: MapDispatchToPropsParam,
+ mergeProps: MergeProps<
+ TStateProps,
+ TDispatchProps,
+ TOwnProps,
+ TMergedProps
+ >,
+ options?: ConnectOptions
+ ): InferableComponentEnhancerWithProps
+ // tslint:enable:no-unnecessary-generics
+}
+*/
+
// createConnect with default args builds the 'official' connect behavior. Calling it with
// different options opens up some testing and extensibility scenarios
export function createConnect({
@@ -86,10 +237,207 @@ export function createConnect({
mergePropsFactories = defaultMergePropsFactories,
selectorFactory = defaultSelectorFactory,
} = {}) {
- return function connect(
- mapStateToProps: MapStateToPropsParam,
- mapDispatchToProps: MapDispatchToPropsParam,
- mergeProps: MergeProps,
+ /* @public */
+ function connect(): InferableComponentEnhancer
+
+ /* @public */
+ function connect<
+ TStateProps = {},
+ no_dispatch = {},
+ TOwnProps = {},
+ State = DefaultRootState
+ >(
+ mapStateToProps: MapStateToPropsParam
+ ): InferableComponentEnhancerWithProps
+
+ /* @public */
+ function connect(
+ mapStateToProps: null | undefined,
+ mapDispatchToProps: MapDispatchToPropsNonObject
+ ): InferableComponentEnhancerWithProps
+
+ /* @public */
+ function connect(
+ mapStateToProps: null | undefined,
+ mapDispatchToProps: MapDispatchToPropsParam
+ ): InferableComponentEnhancerWithProps<
+ ResolveThunks,
+ TOwnProps
+ >
+
+ /* @public */
+ function connect<
+ TStateProps = {},
+ TDispatchProps = {},
+ TOwnProps = {},
+ State = DefaultRootState
+ >(
+ mapStateToProps: MapStateToPropsParam,
+ mapDispatchToProps: MapDispatchToPropsNonObject
+ ): InferableComponentEnhancerWithProps<
+ TStateProps & TDispatchProps,
+ TOwnProps
+ >
+
+ /* @public */
+ function connect<
+ TStateProps = {},
+ TDispatchProps = {},
+ TOwnProps = {},
+ State = DefaultRootState
+ >(
+ mapStateToProps: MapStateToPropsParam,
+ mapDispatchToProps: MapDispatchToPropsParam
+ ): InferableComponentEnhancerWithProps<
+ TStateProps & ResolveThunks,
+ TOwnProps
+ >
+
+ /* @public */
+ function connect<
+ no_state = {},
+ no_dispatch = {},
+ TOwnProps = {},
+ TMergedProps = {}
+ >(
+ mapStateToProps: null | undefined,
+ mapDispatchToProps: null | undefined,
+ mergeProps: MergeProps
+ ): InferableComponentEnhancerWithProps
+
+ /* @public */
+ function connect<
+ TStateProps = {},
+ no_dispatch = {},
+ TOwnProps = {},
+ TMergedProps = {},
+ State = DefaultRootState
+ >(
+ mapStateToProps: MapStateToPropsParam,
+ mapDispatchToProps: null | undefined,
+ mergeProps: MergeProps
+ ): InferableComponentEnhancerWithProps
+
+ /* @public */
+ function connect<
+ no_state = {},
+ TDispatchProps = {},
+ TOwnProps = {},
+ TMergedProps = {}
+ >(
+ mapStateToProps: null | undefined,
+ mapDispatchToProps: MapDispatchToPropsParam,
+ mergeProps: MergeProps
+ ): InferableComponentEnhancerWithProps
+
+ /* @public */
+ // @ts-ignore
+ function connect<
+ TStateProps = {},
+ no_dispatch = {},
+ TOwnProps = {},
+ State = DefaultRootState
+ >(
+ mapStateToProps: MapStateToPropsParam,
+ mapDispatchToProps: null | undefined,
+ mergeProps: null | undefined,
+ options: ConnectOptions
+ ): InferableComponentEnhancerWithProps
+
+ /* @public */
+ function connect(
+ mapStateToProps: null | undefined,
+ mapDispatchToProps: MapDispatchToPropsNonObject,
+ mergeProps: null | undefined,
+ options: ConnectOptions<{}, TStateProps, TOwnProps>
+ ): InferableComponentEnhancerWithProps
+
+ /* @public */
+ function connect(
+ mapStateToProps: null | undefined,
+ mapDispatchToProps: MapDispatchToPropsParam,
+ mergeProps: null | undefined,
+ options: ConnectOptions<{}, TStateProps, TOwnProps>
+ ): InferableComponentEnhancerWithProps<
+ ResolveThunks,
+ TOwnProps
+ >
+
+ /* @public */
+ function connect<
+ TStateProps = {},
+ TDispatchProps = {},
+ TOwnProps = {},
+ State = DefaultRootState
+ >(
+ mapStateToProps: MapStateToPropsParam,
+ mapDispatchToProps: MapDispatchToPropsNonObject,
+ mergeProps: null | undefined,
+ options: ConnectOptions
+ ): InferableComponentEnhancerWithProps<
+ TStateProps & TDispatchProps,
+ TOwnProps
+ >
+
+ /* @public */
+ function connect<
+ TStateProps = {},
+ TDispatchProps = {},
+ TOwnProps = {},
+ State = DefaultRootState
+ >(
+ mapStateToProps: MapStateToPropsParam,
+ mapDispatchToProps: MapDispatchToPropsParam,
+ mergeProps: null | undefined,
+ options: ConnectOptions
+ ): InferableComponentEnhancerWithProps<
+ TStateProps & ResolveThunks,
+ TOwnProps
+ >
+
+ /* @public */
+ function connect<
+ TStateProps = {},
+ TDispatchProps = {},
+ TOwnProps = {},
+ TMergedProps = {},
+ State = DefaultRootState
+ >(
+ mapStateToProps: MapStateToPropsParam,
+ mapDispatchToProps: MapDispatchToPropsParam,
+ mergeProps: MergeProps<
+ TStateProps,
+ TDispatchProps,
+ TOwnProps,
+ TMergedProps
+ >,
+ options?: ConnectOptions
+ ): InferableComponentEnhancerWithProps
+
+ /**
+ * Connects a React component to a Redux store.
+ *
+ * - Without arguments, just wraps the component, without changing the behavior / props
+ *
+ * - If 2 params are passed (3rd param, mergeProps, is skipped), default behavior
+ * is to override ownProps (as stated in the docs), so what remains is everything that's
+ * not a state or dispatch prop
+ *
+ * - When 3rd param is passed, we don't know if ownProps propagate and whether they
+ * should be valid component props, because it depends on mergeProps implementation.
+ * As such, it is the user's responsibility to extend ownProps interface from state or
+ * dispatch props or both when applicable
+ *
+ * @param mapStateToProps A function that extracts values from state
+ * @param mapDispatchToProps Setup for dispatching actions
+ * @param mergeProps Optional callback to merge state and dispatch props together
+ * @param options Options for configuring the connection
+ *
+ */
+ function connect(
+ mapStateToProps?: unknown,
+ mapDispatchToProps?: unknown,
+ mergeProps?: unknown,
{
pure = true,
areStatesEqual = strictEqual,
@@ -97,8 +445,8 @@ export function createConnect({
areStatePropsEqual = shallowEqual,
areMergedPropsEqual = shallowEqual,
...extraOptions
- }: ConnectOptions = {}
- ) {
+ }: ConnectOptions = {}
+ ): unknown {
const initMapStateToProps = match(
mapStateToProps,
// @ts-ignore
@@ -118,8 +466,7 @@ export function createConnect({
'mergeProps'
)
- // @ts-ignore
- return connectHOC(selectorFactory, {
+ return connectHOC(selectorFactory as SelectorFactory, {
// used in error messages
methodName: 'connect',
@@ -143,6 +490,11 @@ export function createConnect({
...extraOptions,
})
}
+
+ return connect
}
-export default /*#__PURE__*/ createConnect()
+/* @public */
+const connect = /*#__PURE__*/ createConnect()
+
+export default connect
diff --git a/src/exports.ts b/src/exports.ts
index c29bc3bff..c8ef76a64 100644
--- a/src/exports.ts
+++ b/src/exports.ts
@@ -20,7 +20,7 @@ import type {
} from './connect/selectorFactory'
import { ReactReduxContext } from './components/Context'
import type { ReactReduxContextValue } from './components/Context'
-import connect from './connect/connect'
+import connect, { ConnectedProps } from './connect/connect'
import { useDispatch, createDispatchHook } from './hooks/useDispatch'
import { useSelector, createSelectorHook } from './hooks/useSelector'
@@ -37,6 +37,7 @@ export type {
MapStateToPropsFactory,
MapStateToPropsParam,
ConnectProps,
+ ConnectedProps,
ConnectAdvancedOptions,
MapDispatchToPropsFunction,
MapDispatchToProps,
diff --git a/src/hooks/useSelector.ts b/src/hooks/useSelector.ts
index c6be70ba4..ee072d9f1 100644
--- a/src/hooks/useSelector.ts
+++ b/src/hooks/useSelector.ts
@@ -52,7 +52,9 @@ function useSelectorWithStoreAndSubscription(
}
} catch (err) {
if (latestSubscriptionCallbackError.current) {
- err.message += `\nThe error may be correlated with this previous error:\n${latestSubscriptionCallbackError.current.stack}\n\n`
+ ;(
+ err as Error
+ ).message += `\nThe error may be correlated with this previous error:\n${latestSubscriptionCallbackError.current.stack}\n\n`
}
throw err
@@ -82,7 +84,7 @@ function useSelectorWithStoreAndSubscription(
// is re-rendered, the selectors are called again, and
// will throw again, if neither props nor store state
// changed
- latestSubscriptionCallbackError.current = err
+ latestSubscriptionCallbackError.current = err as Error
}
forceRender()
diff --git a/src/types.ts b/src/types.ts
index dc2a91f48..affe26c22 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -45,7 +45,7 @@ export interface DispatchProp {
export type AdvancedComponentDecorator = (
component: ComponentType
-) => NamedExoticComponent
+) => ComponentType
/**
* A property P will be present if:
@@ -98,7 +98,7 @@ export type GetProps = C extends ComponentType
export type ConnectedComponent<
C extends ComponentType,
P
-> = NamedExoticComponent> &
+> = ComponentType &
NonReactStatics & {
WrappedComponent: C
}
diff --git a/test/tsconfig.test.json b/test/tsconfig.test.json
new file mode 100644
index 000000000..772b7a17f
--- /dev/null
+++ b/test/tsconfig.test.json
@@ -0,0 +1,17 @@
+{
+ "extends": "../tsconfig.json",
+ "compilerOptions": {
+ "allowSyntheticDefaultImports": true,
+ "esModuleInterop": true,
+ "module": "esnext",
+ "moduleResolution": "node",
+ "emitDeclarationOnly": false,
+ "strict": true,
+ "noEmit": true,
+ "target": "es2018",
+ "jsx": "react",
+ "baseUrl": ".",
+ "skipLibCheck": true,
+ "noImplicitReturns": false
+ }
+}
diff --git a/test/typeTestHelpers.ts b/test/typeTestHelpers.ts
new file mode 100644
index 000000000..08da24571
--- /dev/null
+++ b/test/typeTestHelpers.ts
@@ -0,0 +1,66 @@
+/**
+ * return True if T is `any`, otherwise return False
+ * taken from https://github.com/joonhocho/tsdef
+ *
+ * @internal
+ */
+export type IsAny =
+ // test if we are going the left AND right path in the condition
+ true | false extends (T extends never ? true : false) ? True : False
+
+/**
+ * return True if T is `unknown`, otherwise return False
+ * taken from https://github.com/joonhocho/tsdef
+ *
+ * @internal
+ */
+export type IsUnknown = unknown extends T
+ ? IsAny
+ : False
+
+export function expectType(t: T): T {
+ return t
+}
+
+type Equals = IsAny<
+ T,
+ never,
+ IsAny
+>
+export function expectExactType(t: T) {
+ return >(u: U) => {}
+}
+
+type EnsureUnknown = IsUnknown
+export function expectUnknown>(t: T) {
+ return t
+}
+
+type EnsureAny = IsAny
+export function expectExactAny>(t: T) {
+ return t
+}
+
+type IsNotAny = IsAny
+export function expectNotAny>(t: T): T {
+ return t
+}
+
+expectType('5' as string)
+expectType('5' as const)
+expectType('5' as any)
+expectExactType('5' as const)('5' as const)
+// @ts-expect-error
+expectExactType('5' as string)('5' as const)
+// @ts-expect-error
+expectExactType('5' as any)('5' as const)
+expectUnknown('5' as unknown)
+// @ts-expect-error
+expectUnknown('5' as const)
+// @ts-expect-error
+expectUnknown('5' as any)
+expectExactAny('5' as any)
+// @ts-expect-error
+expectExactAny('5' as const)
+// @ts-expect-error
+expectExactAny('5' as unknown)
diff --git a/test/typetests/react-redux-types.typetest.tsx b/test/typetests/react-redux-types.typetest.tsx
new file mode 100644
index 000000000..26142b0bf
--- /dev/null
+++ b/test/typetests/react-redux-types.typetest.tsx
@@ -0,0 +1,404 @@
+/* eslint-disable @typescript-eslint/no-unused-vars, no-inner-declarations */
+import { Component, ReactElement } from 'react'
+import * as React from 'react'
+import * as ReactDOM from 'react-dom'
+import { Store, Dispatch, bindActionCreators, AnyAction } from 'redux'
+import { connect, Provider, ConnectedProps } from '../../src/index'
+import { expectType } from '../typeTestHelpers'
+
+import objectAssign from 'object-assign'
+
+//
+// Quick Start
+// https://github.com/rackt/react-redux/blob/master/docs/quick-start.md#quick-start
+//
+
+interface CounterState {
+ counter: number
+}
+declare var increment: Function
+
+class Counter extends Component {
+ render() {
+ return
+ }
+}
+
+function mapStateToProps(state: CounterState) {
+ return {
+ value: state.counter,
+ }
+}
+
+// Which action creators does it want to receive by props?
+function mapDispatchToProps(dispatch: Dispatch) {
+ return {
+ onIncrement: () => dispatch(increment()),
+ }
+}
+
+connect(mapStateToProps, mapDispatchToProps)(Counter)
+
+class CounterContainer extends Component {}
+const ConnectedCounterContainer = connect(mapStateToProps)(CounterContainer)
+
+// Ensure connect's first two arguments can be replaced by wrapper functions
+interface ICounterStateProps {
+ value: number
+}
+interface ICounterDispatchProps {
+ onIncrement: () => void
+}
+connect(
+ () => mapStateToProps,
+ () => mapDispatchToProps
+)(Counter)
+// only first argument
+connect(() => mapStateToProps)(
+ Counter
+)
+// wrap only one argument
+connect(
+ mapStateToProps,
+ () => mapDispatchToProps
+)(Counter)
+// with extra arguments
+connect(
+ () => mapStateToProps,
+ () => mapDispatchToProps,
+ (s: ICounterStateProps, d: ICounterDispatchProps) => objectAssign({}, s, d),
+ { pure: true }
+)(Counter)
+
+class App extends Component {
+ render(): React.ReactNode {
+ // ...
+ return null
+ }
+}
+
+const targetEl = document.getElementById('root')
+
+ReactDOM.render({() => }, targetEl)
+
+declare var store: Store
+class MyRootComponent extends Component {}
+class TodoApp extends Component {}
+interface TodoState {
+ todos: string[] | string
+}
+interface TodoProps {
+ userId: number
+}
+interface DispatchProps {
+ addTodo(userId: number, text: string): void
+ // action: Function
+}
+
+const addTodo = (userId: number, text: string) => ({
+ type: 'todos/todoAdded',
+ payload: { userId, text },
+})
+const actionCreators = { addTodo }
+type AddTodoAction = ReturnType
+declare var todoActionCreators: { [type: string]: (...args: any[]) => any }
+declare var counterActionCreators: { [type: string]: (...args: any[]) => any }
+
+ReactDOM.render(
+ {() => },
+ document.body
+)
+
+// Inject just dispatch and don't listen to store
+
+connect()(TodoApp)
+
+// Inject dispatch and every field in the global state
+
+connect((state: TodoState) => state)(TodoApp)
+
+// Inject dispatch and todos
+
+function mapStateToProps2(state: TodoState) {
+ return { todos: state.todos }
+}
+
+export default connect(mapStateToProps2)(TodoApp)
+
+// Inject todos and all action creators (addTodo, completeTodo, ...)
+
+//function mapStateToProps(state) {
+// return { todos: state.todos };
+//}
+
+connect(mapStateToProps2, actionCreators)(TodoApp)
+
+// Inject todos and all action creators (addTodo, completeTodo, ...) as actions
+
+//function mapStateToProps(state) {
+// return { todos: state.todos };
+//}
+
+function mapDispatchToProps2(dispatch: Dispatch) {
+ return { actions: bindActionCreators(actionCreators, dispatch) }
+}
+
+connect(mapStateToProps2, mapDispatchToProps2)(TodoApp)
+
+// Inject todos and a specific action creator (addTodo)
+
+//function mapStateToProps(state) {
+// return { todos: state.todos };
+//}
+
+function mapDispatchToProps3(dispatch: Dispatch) {
+ return bindActionCreators({ addTodo }, dispatch)
+}
+
+connect(mapStateToProps2, mapDispatchToProps3)(TodoApp)
+
+// Inject todos, todoActionCreators as todoActions, and counterActionCreators as counterActions
+
+//function mapStateToProps(state) {
+// return { todos: state.todos };
+//}
+
+function mapDispatchToProps4(dispatch: Dispatch) {
+ return {
+ todoActions: bindActionCreators(todoActionCreators, dispatch),
+ counterActions: bindActionCreators(counterActionCreators, dispatch),
+ }
+}
+
+connect(mapStateToProps2, mapDispatchToProps4)(TodoApp)
+
+// Inject todos, and todoActionCreators and counterActionCreators together as actions
+
+//function mapStateToProps(state) {
+// return { todos: state.todos };
+//}
+
+function mapDispatchToProps5(dispatch: Dispatch) {
+ return {
+ actions: bindActionCreators(
+ objectAssign({}, todoActionCreators, counterActionCreators),
+ dispatch
+ ),
+ }
+}
+
+connect(mapStateToProps2, mapDispatchToProps5)(TodoApp)
+
+// Inject todos, and all todoActionCreators and counterActionCreators directly as props
+
+//function mapStateToProps(state) {
+// return { todos: state.todos };
+//}
+
+function mapDispatchToProps6(dispatch: Dispatch) {
+ return bindActionCreators(
+ objectAssign({}, todoActionCreators, counterActionCreators),
+ dispatch
+ )
+}
+
+connect(mapStateToProps2, mapDispatchToProps6)(TodoApp)
+
+// Inject todos of a specific user depending on props
+
+function mapStateToProps3(state: TodoState, ownProps: TodoProps): TodoState {
+ return { todos: state.todos[ownProps.userId] }
+}
+
+connect(mapStateToProps3)(TodoApp)
+
+// Inject todos of a specific user depending on props, and inject props.userId into the action
+
+//function mapStateToProps(state) {
+// return { todos: state.todos };
+//}
+
+function mergeProps(
+ stateProps: TodoState,
+ dispatchProps: DispatchProps,
+ ownProps: TodoProps
+): { addTodo: (userId: string) => void } & TodoState {
+ return objectAssign({}, ownProps, {
+ todos: stateProps.todos[ownProps.userId],
+ addTodo: (text: string) => dispatchProps.addTodo(ownProps.userId, text),
+ })
+}
+
+connect(mapStateToProps2, actionCreators, mergeProps)(TodoApp)
+
+interface TestProp {
+ property1: number
+ someOtherProperty?: string
+}
+interface TestState {
+ isLoaded: boolean
+ state1: number
+}
+class TestComponent extends Component {}
+const WrappedTestComponent = connect()(TestComponent)
+
+// return value of the connect()(TestComponent) is of the type TestComponent
+let ATestComponent: React.ComponentType
+ATestComponent = TestComponent
+ATestComponent = WrappedTestComponent
+
+let anElement: ReactElement
+;
+;
+;
+
+// @ts-expect-error
+;
+
+class NonComponent {}
+// this doesn't compile
+// @ts-expect-error
+connect()(NonComponent)
+
+// stateless functions
+interface HelloMessageProps {
+ name: string
+}
+function HelloMessage(props: HelloMessageProps) {
+ return Hello {props.name}
+}
+let ConnectedHelloMessage = connect()(HelloMessage)
+ReactDOM.render(
+ ,
+ document.getElementById('content')
+)
+ReactDOM.render(
+ ,
+ document.getElementById('content')
+)
+
+// stateless functions that uses mapStateToProps and mapDispatchToProps
+namespace TestStatelessFunctionWithMapArguments {
+ interface GreetingProps {
+ name: string
+ onClick: () => void
+ }
+
+ function Greeting(props: GreetingProps) {
+ return Hello {props.name}
+ }
+
+ const mapStateToProps = (state: any, ownProps: GreetingProps) => {
+ return {
+ name: 'Connected! ' + ownProps.name,
+ }
+ }
+
+ const mapDispatchToProps = (
+ dispatch: Dispatch,
+ ownProps: GreetingProps
+ ) => {
+ return {
+ onClick: () => {
+ dispatch({ type: 'GREETING', name: ownProps.name })
+ },
+ }
+ }
+
+ const ConnectedGreeting = connect(
+ mapStateToProps,
+ mapDispatchToProps
+ )(Greeting)
+}
+
+// https://github.com/DefinitelyTyped/DefinitelyTyped/issues/8787
+namespace TestTOwnPropsInference {
+ interface OwnProps {
+ own: string
+ }
+
+ interface StateProps {
+ state: string
+ }
+
+ class OwnPropsComponent extends React.Component {
+ render() {
+ return
+ }
+ }
+
+ function mapStateToPropsWithoutOwnProps(state: any): StateProps {
+ return { state: 'string' }
+ }
+
+ function mapStateToPropsWithOwnProps(
+ state: any,
+ ownProps: OwnProps
+ ): StateProps {
+ return { state: 'string' }
+ }
+
+ const ConnectedWithoutOwnProps = connect(mapStateToPropsWithoutOwnProps)(
+ OwnPropsComponent
+ )
+ const ConnectedWithOwnProps = connect(mapStateToPropsWithOwnProps)(
+ OwnPropsComponent
+ )
+ const ConnectedWithTypeHint = connect(
+ mapStateToPropsWithoutOwnProps
+ )(OwnPropsComponent)
+
+ // @ts-expect-error
+ React.createElement(ConnectedWithoutOwnProps, { anything: 'goes!' })
+
+ // This compiles, as expected.
+ React.createElement(ConnectedWithOwnProps, { own: 'string' })
+
+ // This should not compile, which is good.
+ // @ts-expect-error
+ React.createElement(ConnectedWithOwnProps, { missingOwn: true })
+
+ // This compiles, as expected.
+ React.createElement(ConnectedWithTypeHint, { own: 'string' })
+
+ // This should not compile, which is good.
+ // @ts-expect-error
+ React.createElement(ConnectedWithTypeHint, { missingOwn: true })
+}
+
+namespace ConnectedPropsTest {
+ interface RootState {
+ isOn: boolean
+ }
+
+ const mapState1 = (state: RootState) => ({
+ isOn: state.isOn,
+ })
+
+ const mapDispatch1 = {
+ toggleOn: () => ({ type: 'TOGGLE_IS_ON' }),
+ }
+
+ const connector1 = connect(mapState1, mapDispatch1)
+
+ // The inferred type will look like:
+ // {isOn: boolean, toggleOn: () => void}
+ type PropsFromRedux1 = ConnectedProps