diff --git a/.eslintrc b/.eslintrc
index 09dbe7f1e..afacdf656 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -36,7 +36,11 @@
     "react/jsx-uses-react": 1,
     "react/jsx-no-undef": 2,
     "react/jsx-wrap-multilines": 2,
-    "react/no-string-refs": 0
+    "react/no-string-refs": 0,
+    "no-unused-vars": "off",
+    "@typescript-eslint/no-unused-vars": ["error"],
+    "no-redeclare": "off",
+    "@typescript-eslint/no-redeclare": ["error"]
   },
   "plugins": ["@typescript-eslint", "import", "react"],
   "globals": {
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 0809e1767..1551143dd 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -35,3 +35,57 @@ jobs:
 
       - name: Collect coverage
         run: yarn coverage
+
+  test-types:
+    name: Test Types with TypeScript ${{ matrix.ts }}
+
+    needs: [build]
+    runs-on: ubuntu-latest
+    strategy:
+      fail-fast: false
+      matrix:
+        node: ['14.x']
+        ts: ['3.9', '4.0', '4.1', '4.2', '4.3', 'next']
+    steps:
+      - name: Checkout repo
+        uses: actions/checkout@v2
+
+      - name: Use node ${{ matrix.node }}
+        uses: actions/setup-node@v1
+        with:
+          node-version: ${{ matrix.node }}
+
+      - uses: actions/cache@v2
+        with:
+          path: .yarn/cache
+          key: yarn-${{ hashFiles('yarn.lock') }}
+          restore-keys: yarn-
+
+      - name: Install deps
+        run: yarn install
+
+      - name: Install TypeScript ${{ matrix.ts }}
+        run: yarn add typescript@${{ matrix.ts }}
+
+      # - uses: actions/download-artifact@v2
+      #   with:
+      #     name: package
+      #     path: packages/toolkit
+
+      # - name: Install build artifact
+      #   run: yarn add ./package.tgz
+
+      # - run: sed -i -e /@remap-prod-remove-line/d ./tsconfig.base.json ./jest.config.js ./src/tests/*.* ./src/query/tests/*.*
+
+      # - name: "@ts-ignore stuff that didn't exist pre-4.1 in the tests"
+      #   if: ${{ matrix.ts < 4.1 }}
+      #   run: sed -i -e 's/@pre41-ts-ignore/@ts-ignore/' -e  '/pre41-remove-start/,/pre41-remove-end/d' ./src/tests/*.* ./src/query/tests/*.ts*
+
+      # - name: 'disable strictOptionalProperties'
+      #   if: ${{ matrix.ts == 'next' }}
+      #   run: sed -i -e 's|//\(.*strictOptionalProperties.*\)$|\1|' tsconfig.base.json
+
+      - name: Test types
+        run: |
+          yarn tsc --version
+          yarn type-tests
diff --git a/.gitignore b/.gitignore
index 9f3046c66..531827adb 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,6 +3,8 @@ dist
 lib
 coverage
 es
+temp/
+react-redux-*/
 
 .cache
 .yarnrc
diff --git a/etc/react-redux.api.md b/etc/react-redux.api.md
index a5eedaa0e..978c8fe91 100644
--- a/etc/react-redux.api.md
+++ b/etc/react-redux.api.md
@@ -4,7 +4,6 @@
 
 ```ts
 
-/// <reference types="hoist-non-react-statics" />
 /// <reference types="react" />
 
 import { Action } from 'redux';
@@ -15,18 +14,13 @@ import { ComponentClass } from 'react';
 import { ComponentType } from 'react';
 import { Context } from 'react';
 import { Dispatch } from 'redux';
-import { ForwardRefExoticComponent } from 'react';
-import hoistStatics from 'hoist-non-react-statics';
-import { MemoExoticComponent } from 'react';
-import { NamedExoticComponent } from 'react';
-import { NonReactStatics } from 'hoist-non-react-statics';
+import type { NonReactStatics } from 'hoist-non-react-statics';
 import { default as React_2 } from 'react';
 import { ReactNode } from 'react';
-import { RefAttributes } from 'react';
 import { Store } from 'redux';
 
 // @public (undocumented)
-export type AdvancedComponentDecorator<TProps, TOwnProps> = (component: ComponentType<TProps>) => NamedExoticComponent<TOwnProps>;
+export type AdvancedComponentDecorator<TProps, TOwnProps> = (component: ComponentType<TProps>) => ComponentType<TOwnProps>;
 
 // @public (undocumented)
 export type AnyIfEmpty<T extends object> = keyof T extends never ? any : T;
@@ -34,30 +28,26 @@ export type AnyIfEmpty<T extends object> = keyof T extends never ? any : T;
 export { batch }
 
 // @public (undocumented)
-export const connect: (mapStateToProps: MapStateToPropsParam<unknown, unknown, DefaultRootState>, mapDispatchToProps: unknown, mergeProps: MergeProps<unknown, unknown, unknown, unknown>, { pure, areStatesEqual, areOwnPropsEqual, areStatePropsEqual, areMergedPropsEqual, ...extraOptions }?: ConnectOptions<DefaultRootState, {}, {}, {}>) => <WC extends ComponentType<    {}>>(WrappedComponent: WC) => (ForwardRefExoticComponent<RefAttributes<unknown>> & {
-    WrappedComponent: WC;
-} & NonReactStatics<WC, {}>) | ((({
-    <TOwnProps>(props: ConnectProps & TOwnProps): JSX.Element;
-    displayName: string;
-} | MemoExoticComponent<    {
-<TOwnProps>(props: ConnectProps & TOwnProps): JSX.Element;
-displayName: string;
-}>) & {
-    WrappedComponent: WC;
-}) & NonReactStatics<WC, {}>);
-
-// @public (undocumented)
-export function connectAdvanced<S, TProps, TOwnProps, TFactoryOptions extends AnyObject = {}>(selectorFactory: SelectorFactory<S, TProps, unknown, unknown>, { getDisplayName, methodName, shouldHandleStateChanges, forwardRef, context, ...connectOptions }?: ConnectAdvancedOptions & Partial<TFactoryOptions>): <WC extends React_2.ComponentType<{}>>(WrappedComponent: WC) => (React_2.ForwardRefExoticComponent<React_2.RefAttributes<unknown>> & {
-    WrappedComponent: WC;
-} & hoistStatics.NonReactStatics<WC, {}>) | ((({
-    <TOwnProps_1>(props: ConnectProps & TOwnProps_1): JSX.Element;
-    displayName: string;
-} | React_2.MemoExoticComponent<{
-    <TOwnProps_1>(props: ConnectProps & TOwnProps_1): JSX.Element;
-    displayName: string;
-}>) & {
-    WrappedComponent: WC;
-}) & hoistStatics.NonReactStatics<WC, {}>);
+export const connect: {
+    (): InferableComponentEnhancer<DispatchProp>;
+    <TStateProps = {}, no_dispatch = {}, TOwnProps = {}, State = DefaultRootState>(mapStateToProps: MapStateToPropsParam<TStateProps, TOwnProps, State>): InferableComponentEnhancerWithProps<TStateProps & DispatchProp<AnyAction>, TOwnProps>;
+    <no_state = {}, TDispatchProps = {}, TOwnProps_1 = {}>(mapStateToProps: null | undefined, mapDispatchToProps: MapDispatchToPropsNonObject<TDispatchProps, TOwnProps_1>): InferableComponentEnhancerWithProps<TDispatchProps, TOwnProps_1>;
+    <no_state_1 = {}, TDispatchProps_1 = {}, TOwnProps_2 = {}>(mapStateToProps: null | undefined, mapDispatchToProps: MapDispatchToPropsParam<TDispatchProps_1, TOwnProps_2>): InferableComponentEnhancerWithProps<ResolveThunks<TDispatchProps_1>, TOwnProps_2>;
+    <TStateProps_1 = {}, TDispatchProps_2 = {}, TOwnProps_3 = {}, State_1 = DefaultRootState>(mapStateToProps: MapStateToPropsParam<TStateProps_1, TOwnProps_3, State_1>, mapDispatchToProps: MapDispatchToPropsNonObject<TDispatchProps_2, TOwnProps_3>): InferableComponentEnhancerWithProps<TStateProps_1 & TDispatchProps_2, TOwnProps_3>;
+    <TStateProps_2 = {}, TDispatchProps_3 = {}, TOwnProps_4 = {}, State_2 = DefaultRootState>(mapStateToProps: MapStateToPropsParam<TStateProps_2, TOwnProps_4, State_2>, mapDispatchToProps: MapDispatchToPropsParam<TDispatchProps_3, TOwnProps_4>): InferableComponentEnhancerWithProps<TStateProps_2 & ResolveThunks<TDispatchProps_3>, TOwnProps_4>;
+    <no_state_2 = {}, no_dispatch_1 = {}, TOwnProps_5 = {}, TMergedProps = {}>(mapStateToProps: null | undefined, mapDispatchToProps: null | undefined, mergeProps: MergeProps<undefined, undefined, TOwnProps_5, TMergedProps>): InferableComponentEnhancerWithProps<TMergedProps, TOwnProps_5>;
+    <TStateProps_3 = {}, no_dispatch_2 = {}, TOwnProps_6 = {}, TMergedProps_1 = {}, State_3 = DefaultRootState>(mapStateToProps: MapStateToPropsParam<TStateProps_3, TOwnProps_6, State_3>, mapDispatchToProps: null | undefined, mergeProps: MergeProps<TStateProps_3, undefined, TOwnProps_6, TMergedProps_1>): InferableComponentEnhancerWithProps<TMergedProps_1, TOwnProps_6>;
+    <no_state_3 = {}, TDispatchProps_4 = {}, TOwnProps_7 = {}, TMergedProps_2 = {}>(mapStateToProps: null | undefined, mapDispatchToProps: MapDispatchToPropsParam<TDispatchProps_4, TOwnProps_7>, mergeProps: MergeProps<undefined, TDispatchProps_4, TOwnProps_7, TMergedProps_2>): InferableComponentEnhancerWithProps<TMergedProps_2, TOwnProps_7>;
+    <TStateProps_4 = {}, no_dispatch_3 = {}, TOwnProps_8 = {}, State_4 = DefaultRootState>(mapStateToProps: MapStateToPropsParam<TStateProps_4, TOwnProps_8, State_4>, mapDispatchToProps: null | undefined, mergeProps: null | undefined, options: ConnectOptions<State_4, TStateProps_4, TOwnProps_8, {}>): InferableComponentEnhancerWithProps<DispatchProp<AnyAction> & TStateProps_4, TOwnProps_8>;
+    <TStateProps_5 = {}, TDispatchProps_5 = {}, TOwnProps_9 = {}>(mapStateToProps: null | undefined, mapDispatchToProps: MapDispatchToPropsNonObject<TDispatchProps_5, TOwnProps_9>, mergeProps: null | undefined, options: ConnectOptions<{}, TStateProps_5, TOwnProps_9, {}>): InferableComponentEnhancerWithProps<TDispatchProps_5, TOwnProps_9>;
+    <TStateProps_6 = {}, TDispatchProps_6 = {}, TOwnProps_10 = {}>(mapStateToProps: null | undefined, mapDispatchToProps: MapDispatchToPropsParam<TDispatchProps_6, TOwnProps_10>, mergeProps: null | undefined, options: ConnectOptions<{}, TStateProps_6, TOwnProps_10, {}>): InferableComponentEnhancerWithProps<ResolveThunks<TDispatchProps_6>, TOwnProps_10>;
+    <TStateProps_7 = {}, TDispatchProps_7 = {}, TOwnProps_11 = {}, State_5 = DefaultRootState>(mapStateToProps: MapStateToPropsParam<TStateProps_7, TOwnProps_11, State_5>, mapDispatchToProps: MapDispatchToPropsNonObject<TDispatchProps_7, TOwnProps_11>, mergeProps: null | undefined, options: ConnectOptions<State_5, TStateProps_7, TOwnProps_11, {}>): InferableComponentEnhancerWithProps<TStateProps_7 & TDispatchProps_7, TOwnProps_11>;
+    <TStateProps_8 = {}, TDispatchProps_8 = {}, TOwnProps_12 = {}, State_6 = DefaultRootState>(mapStateToProps: MapStateToPropsParam<TStateProps_8, TOwnProps_12, State_6>, mapDispatchToProps: MapDispatchToPropsParam<TDispatchProps_8, TOwnProps_12>, mergeProps: null | undefined, options: ConnectOptions<State_6, TStateProps_8, TOwnProps_12, {}>): InferableComponentEnhancerWithProps<TStateProps_8 & ResolveThunks<TDispatchProps_8>, TOwnProps_12>;
+    <TStateProps_9 = {}, TDispatchProps_9 = {}, TOwnProps_13 = {}, TMergedProps_3 = {}, State_7 = DefaultRootState>(mapStateToProps: MapStateToPropsParam<TStateProps_9, TOwnProps_13, State_7>, mapDispatchToProps: MapDispatchToPropsParam<TDispatchProps_9, TOwnProps_13>, mergeProps: MergeProps<TStateProps_9, TDispatchProps_9, TOwnProps_13, TMergedProps_3>, options?: ConnectOptions<State_7, TStateProps_9, TOwnProps_13, TMergedProps_3> | undefined): InferableComponentEnhancerWithProps<TMergedProps_3, TOwnProps_13>;
+};
+
+// @public (undocumented)
+export function connectAdvanced<S, TProps, TOwnProps, TFactoryOptions = {}>(selectorFactory: SelectorFactory<S, TProps, unknown, unknown>, { getDisplayName, methodName, shouldHandleStateChanges, forwardRef, context, ...connectOptions }?: ConnectAdvancedOptions & Partial<TFactoryOptions>): AdvancedComponentDecorator<TProps, TOwnProps & ConnectProps>;
 
 // @public (undocumented)
 export interface ConnectAdvancedOptions {
@@ -76,10 +66,13 @@ export interface ConnectAdvancedOptions {
 }
 
 // @public (undocumented)
-export type ConnectedComponent<C extends ComponentType<any>, P> = NamedExoticComponent<JSX.LibraryManagedAttributes<C, P>> & NonReactStatics<C> & {
+export type ConnectedComponent<C extends ComponentType<any>, P> = ComponentType<P> & NonReactStatics<C> & {
     WrappedComponent: C;
 };
 
+// @public
+export type ConnectedProps<TConnector> = TConnector extends InferableComponentEnhancerWithProps<infer TInjectedProps, any> ? unknown extends TInjectedProps ? TConnector extends InferableComponentEnhancer<infer TInjectedProps> ? 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<S, TProps, TOwnProps, TFactoryOptions = {}>(
   /*
     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<WC extends React.ComponentType>(
-    WrappedComponent: WC
-  ) {
+  type WrappedComponentProps = TOwnProps & ConnectProps
+
+  /*
+  return function wrapWithConnect<
+    WC extends React.ComponentType<
+      Matching<DispatchProp<AnyAction>, GetProps<WC>>
+    >
+  >(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 <Connect {...props} reactReduxForwardedRef={ref} />
       })
 
-      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> =
+  TConnector extends InferableComponentEnhancerWithProps<
+    infer TInjectedProps,
+    any
+  >
+    ? unknown extends TInjectedProps
+      ? TConnector extends InferableComponentEnhancer<infer TInjectedProps>
+        ? TInjectedProps
+        : never
+      : TInjectedProps
+    : never
+
 export interface ConnectOptions<
   State = DefaultRootState,
   TStateProps = {},
@@ -77,6 +101,133 @@ export interface ConnectOptions<
   forwardRef?: boolean | undefined
 }
 
+/*
+export interface Connect<DefaultState = DefaultRootState> {
+  // tslint:disable:no-unnecessary-generics
+  (): InferableComponentEnhancer<DispatchProp>
+
+  <TStateProps = {}, no_dispatch = {}, TOwnProps = {}, State = DefaultState>(
+    mapStateToProps: MapStateToPropsParam<TStateProps, TOwnProps, State>
+  ): InferableComponentEnhancerWithProps<TStateProps & DispatchProp, TOwnProps>
+
+  <no_state = {}, TDispatchProps = {}, TOwnProps = {}>(
+    mapStateToProps: null | undefined,
+    mapDispatchToProps: MapDispatchToPropsNonObject<TDispatchProps, TOwnProps>
+  ): InferableComponentEnhancerWithProps<TDispatchProps, TOwnProps>
+
+  <no_state = {}, TDispatchProps = {}, TOwnProps = {}>(
+    mapStateToProps: null | undefined,
+    mapDispatchToProps: MapDispatchToPropsParam<TDispatchProps, TOwnProps>
+  ): InferableComponentEnhancerWithProps<
+    ResolveThunks<TDispatchProps>,
+    TOwnProps
+  >
+
+  <TStateProps = {}, TDispatchProps = {}, TOwnProps = {}, State = DefaultState>(
+    mapStateToProps: MapStateToPropsParam<TStateProps, TOwnProps, State>,
+    mapDispatchToProps: MapDispatchToPropsNonObject<TDispatchProps, TOwnProps>
+  ): InferableComponentEnhancerWithProps<
+    TStateProps & TDispatchProps,
+    TOwnProps
+  >
+
+  <TStateProps = {}, TDispatchProps = {}, TOwnProps = {}, State = DefaultState>(
+    mapStateToProps: MapStateToPropsParam<TStateProps, TOwnProps, State>,
+    mapDispatchToProps: MapDispatchToPropsParam<TDispatchProps, TOwnProps>
+  ): InferableComponentEnhancerWithProps<
+    TStateProps & ResolveThunks<TDispatchProps>,
+    TOwnProps
+  >
+
+  <no_state = {}, no_dispatch = {}, TOwnProps = {}, TMergedProps = {}>(
+    mapStateToProps: null | undefined,
+    mapDispatchToProps: null | undefined,
+    mergeProps: MergeProps<undefined, undefined, TOwnProps, TMergedProps>
+  ): InferableComponentEnhancerWithProps<TMergedProps, TOwnProps>
+
+  <
+    TStateProps = {},
+    no_dispatch = {},
+    TOwnProps = {},
+    TMergedProps = {},
+    State = DefaultState
+  >(
+    mapStateToProps: MapStateToPropsParam<TStateProps, TOwnProps, State>,
+    mapDispatchToProps: null | undefined,
+    mergeProps: MergeProps<TStateProps, undefined, TOwnProps, TMergedProps>
+  ): InferableComponentEnhancerWithProps<TMergedProps, TOwnProps>
+
+  <no_state = {}, TDispatchProps = {}, TOwnProps = {}, TMergedProps = {}>(
+    mapStateToProps: null | undefined,
+    mapDispatchToProps: MapDispatchToPropsParam<TDispatchProps, TOwnProps>,
+    mergeProps: MergeProps<undefined, TDispatchProps, TOwnProps, TMergedProps>
+  ): InferableComponentEnhancerWithProps<TMergedProps, TOwnProps>
+
+  <TStateProps = {}, no_dispatch = {}, TOwnProps = {}, State = DefaultState>(
+    mapStateToProps: MapStateToPropsParam<TStateProps, TOwnProps, State>,
+    mapDispatchToProps: null | undefined,
+    mergeProps: null | undefined,
+    options: ConnectOptions<State, TStateProps, TOwnProps>
+  ): InferableComponentEnhancerWithProps<DispatchProp & TStateProps, TOwnProps>
+
+  <TStateProps = {}, TDispatchProps = {}, TOwnProps = {}>(
+    mapStateToProps: null | undefined,
+    mapDispatchToProps: MapDispatchToPropsNonObject<TDispatchProps, TOwnProps>,
+    mergeProps: null | undefined,
+    options: ConnectOptions<{}, TStateProps, TOwnProps>
+  ): InferableComponentEnhancerWithProps<TDispatchProps, TOwnProps>
+
+  <TStateProps = {}, TDispatchProps = {}, TOwnProps = {}>(
+    mapStateToProps: null | undefined,
+    mapDispatchToProps: MapDispatchToPropsParam<TDispatchProps, TOwnProps>,
+    mergeProps: null | undefined,
+    options: ConnectOptions<{}, TStateProps, TOwnProps>
+  ): InferableComponentEnhancerWithProps<
+    ResolveThunks<TDispatchProps>,
+    TOwnProps
+  >
+
+  <TStateProps = {}, TDispatchProps = {}, TOwnProps = {}, State = DefaultState>(
+    mapStateToProps: MapStateToPropsParam<TStateProps, TOwnProps, State>,
+    mapDispatchToProps: MapDispatchToPropsNonObject<TDispatchProps, TOwnProps>,
+    mergeProps: null | undefined,
+    options: ConnectOptions<State, TStateProps, TOwnProps>
+  ): InferableComponentEnhancerWithProps<
+    TStateProps & TDispatchProps,
+    TOwnProps
+  >
+
+  <TStateProps = {}, TDispatchProps = {}, TOwnProps = {}, State = DefaultState>(
+    mapStateToProps: MapStateToPropsParam<TStateProps, TOwnProps, State>,
+    mapDispatchToProps: MapDispatchToPropsParam<TDispatchProps, TOwnProps>,
+    mergeProps: null | undefined,
+    options: ConnectOptions<State, TStateProps, TOwnProps>
+  ): InferableComponentEnhancerWithProps<
+    TStateProps & ResolveThunks<TDispatchProps>,
+    TOwnProps
+  >
+
+  <
+    TStateProps = {},
+    TDispatchProps = {},
+    TOwnProps = {},
+    TMergedProps = {},
+    State = DefaultState
+  >(
+    mapStateToProps: MapStateToPropsParam<TStateProps, TOwnProps, State>,
+    mapDispatchToProps: MapDispatchToPropsParam<TDispatchProps, TOwnProps>,
+    mergeProps: MergeProps<
+      TStateProps,
+      TDispatchProps,
+      TOwnProps,
+      TMergedProps
+    >,
+    options?: ConnectOptions<State, TStateProps, TOwnProps, TMergedProps>
+  ): InferableComponentEnhancerWithProps<TMergedProps, TOwnProps>
+  // 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<unknown, unknown>,
-    mapDispatchToProps: MapDispatchToPropsParam<unknown, unknown>,
-    mergeProps: MergeProps<unknown, unknown, unknown, unknown>,
+  /* @public */
+  function connect(): InferableComponentEnhancer<DispatchProp>
+
+  /* @public */
+  function connect<
+    TStateProps = {},
+    no_dispatch = {},
+    TOwnProps = {},
+    State = DefaultRootState
+  >(
+    mapStateToProps: MapStateToPropsParam<TStateProps, TOwnProps, State>
+  ): InferableComponentEnhancerWithProps<TStateProps & DispatchProp, TOwnProps>
+
+  /* @public */
+  function connect<no_state = {}, TDispatchProps = {}, TOwnProps = {}>(
+    mapStateToProps: null | undefined,
+    mapDispatchToProps: MapDispatchToPropsNonObject<TDispatchProps, TOwnProps>
+  ): InferableComponentEnhancerWithProps<TDispatchProps, TOwnProps>
+
+  /* @public */
+  function connect<no_state = {}, TDispatchProps = {}, TOwnProps = {}>(
+    mapStateToProps: null | undefined,
+    mapDispatchToProps: MapDispatchToPropsParam<TDispatchProps, TOwnProps>
+  ): InferableComponentEnhancerWithProps<
+    ResolveThunks<TDispatchProps>,
+    TOwnProps
+  >
+
+  /* @public */
+  function connect<
+    TStateProps = {},
+    TDispatchProps = {},
+    TOwnProps = {},
+    State = DefaultRootState
+  >(
+    mapStateToProps: MapStateToPropsParam<TStateProps, TOwnProps, State>,
+    mapDispatchToProps: MapDispatchToPropsNonObject<TDispatchProps, TOwnProps>
+  ): InferableComponentEnhancerWithProps<
+    TStateProps & TDispatchProps,
+    TOwnProps
+  >
+
+  /* @public */
+  function connect<
+    TStateProps = {},
+    TDispatchProps = {},
+    TOwnProps = {},
+    State = DefaultRootState
+  >(
+    mapStateToProps: MapStateToPropsParam<TStateProps, TOwnProps, State>,
+    mapDispatchToProps: MapDispatchToPropsParam<TDispatchProps, TOwnProps>
+  ): InferableComponentEnhancerWithProps<
+    TStateProps & ResolveThunks<TDispatchProps>,
+    TOwnProps
+  >
+
+  /* @public */
+  function connect<
+    no_state = {},
+    no_dispatch = {},
+    TOwnProps = {},
+    TMergedProps = {}
+  >(
+    mapStateToProps: null | undefined,
+    mapDispatchToProps: null | undefined,
+    mergeProps: MergeProps<undefined, undefined, TOwnProps, TMergedProps>
+  ): InferableComponentEnhancerWithProps<TMergedProps, TOwnProps>
+
+  /* @public */
+  function connect<
+    TStateProps = {},
+    no_dispatch = {},
+    TOwnProps = {},
+    TMergedProps = {},
+    State = DefaultRootState
+  >(
+    mapStateToProps: MapStateToPropsParam<TStateProps, TOwnProps, State>,
+    mapDispatchToProps: null | undefined,
+    mergeProps: MergeProps<TStateProps, undefined, TOwnProps, TMergedProps>
+  ): InferableComponentEnhancerWithProps<TMergedProps, TOwnProps>
+
+  /* @public */
+  function connect<
+    no_state = {},
+    TDispatchProps = {},
+    TOwnProps = {},
+    TMergedProps = {}
+  >(
+    mapStateToProps: null | undefined,
+    mapDispatchToProps: MapDispatchToPropsParam<TDispatchProps, TOwnProps>,
+    mergeProps: MergeProps<undefined, TDispatchProps, TOwnProps, TMergedProps>
+  ): InferableComponentEnhancerWithProps<TMergedProps, TOwnProps>
+
+  /* @public */
+  // @ts-ignore
+  function connect<
+    TStateProps = {},
+    no_dispatch = {},
+    TOwnProps = {},
+    State = DefaultRootState
+  >(
+    mapStateToProps: MapStateToPropsParam<TStateProps, TOwnProps, State>,
+    mapDispatchToProps: null | undefined,
+    mergeProps: null | undefined,
+    options: ConnectOptions<State, TStateProps, TOwnProps>
+  ): InferableComponentEnhancerWithProps<DispatchProp & TStateProps, TOwnProps>
+
+  /* @public */
+  function connect<TStateProps = {}, TDispatchProps = {}, TOwnProps = {}>(
+    mapStateToProps: null | undefined,
+    mapDispatchToProps: MapDispatchToPropsNonObject<TDispatchProps, TOwnProps>,
+    mergeProps: null | undefined,
+    options: ConnectOptions<{}, TStateProps, TOwnProps>
+  ): InferableComponentEnhancerWithProps<TDispatchProps, TOwnProps>
+
+  /* @public */
+  function connect<TStateProps = {}, TDispatchProps = {}, TOwnProps = {}>(
+    mapStateToProps: null | undefined,
+    mapDispatchToProps: MapDispatchToPropsParam<TDispatchProps, TOwnProps>,
+    mergeProps: null | undefined,
+    options: ConnectOptions<{}, TStateProps, TOwnProps>
+  ): InferableComponentEnhancerWithProps<
+    ResolveThunks<TDispatchProps>,
+    TOwnProps
+  >
+
+  /* @public */
+  function connect<
+    TStateProps = {},
+    TDispatchProps = {},
+    TOwnProps = {},
+    State = DefaultRootState
+  >(
+    mapStateToProps: MapStateToPropsParam<TStateProps, TOwnProps, State>,
+    mapDispatchToProps: MapDispatchToPropsNonObject<TDispatchProps, TOwnProps>,
+    mergeProps: null | undefined,
+    options: ConnectOptions<State, TStateProps, TOwnProps>
+  ): InferableComponentEnhancerWithProps<
+    TStateProps & TDispatchProps,
+    TOwnProps
+  >
+
+  /* @public */
+  function connect<
+    TStateProps = {},
+    TDispatchProps = {},
+    TOwnProps = {},
+    State = DefaultRootState
+  >(
+    mapStateToProps: MapStateToPropsParam<TStateProps, TOwnProps, State>,
+    mapDispatchToProps: MapDispatchToPropsParam<TDispatchProps, TOwnProps>,
+    mergeProps: null | undefined,
+    options: ConnectOptions<State, TStateProps, TOwnProps>
+  ): InferableComponentEnhancerWithProps<
+    TStateProps & ResolveThunks<TDispatchProps>,
+    TOwnProps
+  >
+
+  /* @public */
+  function connect<
+    TStateProps = {},
+    TDispatchProps = {},
+    TOwnProps = {},
+    TMergedProps = {},
+    State = DefaultRootState
+  >(
+    mapStateToProps: MapStateToPropsParam<TStateProps, TOwnProps, State>,
+    mapDispatchToProps: MapDispatchToPropsParam<TDispatchProps, TOwnProps>,
+    mergeProps: MergeProps<
+      TStateProps,
+      TDispatchProps,
+      TOwnProps,
+      TMergedProps
+    >,
+    options?: ConnectOptions<State, TStateProps, TOwnProps, TMergedProps>
+  ): InferableComponentEnhancerWithProps<TMergedProps, TOwnProps>
+
+  /**
+   * 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, unknown, unknown, unknown> = {}
+  ): 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<any, any, any, any>, {
       // 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<TStoreState, TSelectedState>(
     }
   } 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<TStoreState, TSelectedState>(
         // 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<A extends Action = AnyAction> {
 
 export type AdvancedComponentDecorator<TProps, TOwnProps> = (
   component: ComponentType<TProps>
-) => NamedExoticComponent<TOwnProps>
+) => ComponentType<TOwnProps>
 
 /**
  * A property P will be present if:
@@ -98,7 +98,7 @@ export type GetProps<C> = C extends ComponentType<infer P>
 export type ConnectedComponent<
   C extends ComponentType<any>,
   P
-> = NamedExoticComponent<JSX.LibraryManagedAttributes<C, P>> &
+> = ComponentType<P> &
   NonReactStatics<C> & {
     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<T, True, False = never> =
+  // 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<T, True, False = never> = unknown extends T
+  ? IsAny<T, False, True>
+  : False
+
+export function expectType<T>(t: T): T {
+  return t
+}
+
+type Equals<T, U> = IsAny<
+  T,
+  never,
+  IsAny<U, never, [T] extends [U] ? ([U] extends [T] ? any : never) : never>
+>
+export function expectExactType<T>(t: T) {
+  return <U extends Equals<T, U>>(u: U) => {}
+}
+
+type EnsureUnknown<T extends any> = IsUnknown<T, any, never>
+export function expectUnknown<T extends EnsureUnknown<T>>(t: T) {
+  return t
+}
+
+type EnsureAny<T extends any> = IsAny<T, any, never>
+export function expectExactAny<T extends EnsureAny<T>>(t: T) {
+  return t
+}
+
+type IsNotAny<T> = IsAny<T, never, any>
+export function expectNotAny<T extends IsNotAny<T>>(t: T): T {
+  return t
+}
+
+expectType<string>('5' as string)
+expectType<string>('5' as const)
+expectType<string>('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<any, any> {
+  render() {
+    return <button onClick={this.props.onIncrement}>{this.props.value}</button>
+  }
+}
+
+function mapStateToProps(state: CounterState) {
+  return {
+    value: state.counter,
+  }
+}
+
+// Which action creators does it want to receive by props?
+function mapDispatchToProps(dispatch: Dispatch<AnyAction>) {
+  return {
+    onIncrement: () => dispatch(increment()),
+  }
+}
+
+connect(mapStateToProps, mapDispatchToProps)(Counter)
+
+class CounterContainer extends Component<any, any> {}
+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<ICounterStateProps, ICounterDispatchProps, {}, CounterState>(
+  () => mapStateToProps,
+  () => mapDispatchToProps
+)(Counter)
+// only first argument
+connect<ICounterStateProps, {}, {}, CounterState>(() => mapStateToProps)(
+  Counter
+)
+// wrap only one argument
+connect<ICounterStateProps, ICounterDispatchProps, {}, CounterState>(
+  mapStateToProps,
+  () => mapDispatchToProps
+)(Counter)
+// with extra arguments
+connect<ICounterStateProps, ICounterDispatchProps, {}, {}, CounterState>(
+  () => mapStateToProps,
+  () => mapDispatchToProps,
+  (s: ICounterStateProps, d: ICounterDispatchProps) => objectAssign({}, s, d),
+  { pure: true }
+)(Counter)
+
+class App extends Component<any, any> {
+  render(): React.ReactNode {
+    // ...
+    return null
+  }
+}
+
+const targetEl = document.getElementById('root')
+
+ReactDOM.render(<Provider store={store}>{() => <App />}</Provider>, targetEl)
+
+declare var store: Store<TodoState>
+class MyRootComponent extends Component<any, any> {}
+class TodoApp extends Component<any, any> {}
+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<typeof addTodo>
+declare var todoActionCreators: { [type: string]: (...args: any[]) => any }
+declare var counterActionCreators: { [type: string]: (...args: any[]) => any }
+
+ReactDOM.render(
+  <Provider store={store}>{() => <MyRootComponent />}</Provider>,
+  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<AnyAction>) {
+  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<AnyAction>) {
+  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<AnyAction>) {
+  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<AnyAction>) {
+  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<AnyAction>) {
+  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<TestProp, TestState> {}
+const WrappedTestComponent = connect()(TestComponent)
+
+// return value of the connect()(TestComponent) is of the type TestComponent
+let ATestComponent: React.ComponentType<TestProp>
+ATestComponent = TestComponent
+ATestComponent = WrappedTestComponent
+
+let anElement: ReactElement<TestProp>
+;<TestComponent property1={42} />
+;<WrappedTestComponent property1={42} />
+;<ATestComponent property1={42} />
+
+// @ts-expect-error
+;<ATestComponent property1={42} dummyField={123} />
+
+class NonComponent {}
+// this doesn't compile
+// @ts-expect-error
+connect()(NonComponent)
+
+// stateless functions
+interface HelloMessageProps {
+  name: string
+}
+function HelloMessage(props: HelloMessageProps) {
+  return <div>Hello {props.name}</div>
+}
+let ConnectedHelloMessage = connect()(HelloMessage)
+ReactDOM.render(
+  <HelloMessage name="Sebastian" />,
+  document.getElementById('content')
+)
+ReactDOM.render(
+  <ConnectedHelloMessage name="Sebastian" />,
+  document.getElementById('content')
+)
+
+// stateless functions that uses mapStateToProps and mapDispatchToProps
+namespace TestStatelessFunctionWithMapArguments {
+  interface GreetingProps {
+    name: string
+    onClick: () => void
+  }
+
+  function Greeting(props: GreetingProps) {
+    return <div>Hello {props.name}</div>
+  }
+
+  const mapStateToProps = (state: any, ownProps: GreetingProps) => {
+    return {
+      name: 'Connected! ' + ownProps.name,
+    }
+  }
+
+  const mapDispatchToProps = (
+    dispatch: Dispatch<AnyAction>,
+    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<OwnProps & StateProps, {}> {
+    render() {
+      return <div />
+    }
+  }
+
+  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<StateProps, {}, OwnProps>(
+    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<typeof connector1>
+
+  expectType<{ isOn: boolean; toggleOn: () => void }>({} as PropsFromRedux1)
+
+  const exampleThunk = (id: number) => async (dispatch: Dispatch) => {
+    return 'test'
+  }
+
+  const mapDispatch2 = { exampleThunk }
+
+  // Connect should "resolve thunks", so that instead of typing the return value of the
+  // prop as the thunk function, it dives down and uses the return value of the thunk function itself
+  const connector2 = connect(null, mapDispatch2)
+  type PropsFromRedux2 = ConnectedProps<typeof connector2>
+
+  expectType<{ exampleThunk: (id: number) => Promise<string> }>(
+    {} as PropsFromRedux2
+  )
+}
diff --git a/test/typetests/tsconfig.json b/test/typetests/tsconfig.json
new file mode 100644
index 000000000..38ca0b13b
--- /dev/null
+++ b/test/typetests/tsconfig.json
@@ -0,0 +1,3 @@
+{
+  "extends": "../tsconfig.test.json"
+}
diff --git a/yarn.lock b/yarn.lock
index 9094f2a79..f832e723b 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3577,6 +3577,13 @@ __metadata:
   languageName: node
   linkType: hard
 
+"@types/object-assign@npm:^4.0.30":
+  version: 4.0.30
+  resolution: "@types/object-assign@npm:4.0.30"
+  checksum: dd9b5d5e183707bf4c1d911a98aa2d004b08ce901b8b52ac5231267997f5ebb933fad03528e8a39f1090d5cb572f6b0f0dd797912ee9815b8bb12312c85a43fc
+  languageName: node
+  linkType: hard
+
 "@types/parse-json@npm:^4.0.0":
   version: 4.0.0
   resolution: "@types/parse-json@npm:4.0.0"
@@ -3612,15 +3619,33 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@types/react-redux@npm:^7.1.16":
-  version: 7.1.16
-  resolution: "@types/react-redux@npm:7.1.16"
+"@types/react-dom@npm:^17.0.9":
+  version: 17.0.9
+  resolution: "@types/react-dom@npm:17.0.9"
+  dependencies:
+    "@types/react": "*"
+  checksum: 82da85bcfba524fb83946df50e433b8cc942dd30f5f3f6e0756816960ce4b85db6faa2da9f2b83124087fbe0d124580edf7b1017628b78fb28fee057e91512cb
+  languageName: node
+  linkType: hard
+
+"@types/react-is@npm:^17.0.1":
+  version: 17.0.1
+  resolution: "@types/react-is@npm:17.0.1"
+  dependencies:
+    "@types/react": "*"
+  checksum: d0ea951ddcde54bcaf6ea227595b7345085c316f2791277a4acfdbbe63cc0de098264269c5463050d7e1308042303556980f9dda32eb2f619eace2efd4e9bdd4
+  languageName: node
+  linkType: hard
+
+"@types/react-redux@npm:^7.1.18":
+  version: 7.1.18
+  resolution: "@types/react-redux@npm:7.1.18"
   dependencies:
     "@types/hoist-non-react-statics": ^3.3.0
     "@types/react": "*"
     hoist-non-react-statics: ^3.3.0
     redux: ^4.0.0
-  checksum: c19c8f94dbadae42e9622c0b1b38324bbc6f5997fdc3989e71b202bf750d5869f73bb349ca4102f624188c2212c7907d3c3594449f099818abc87d1478d20996
+  checksum: b247ff7ce31cede226f4606571bf975aeec91fe911e65e72cecaaac7234d4d694a7be0791419bb4259c7012b662a96267f694daaacbc18f3157fc7f955af55c9
   languageName: node
   linkType: hard
 
@@ -3644,6 +3669,17 @@ __metadata:
   languageName: node
   linkType: hard
 
+"@types/react@npm:^17.0.14":
+  version: 17.0.14
+  resolution: "@types/react@npm:17.0.14"
+  dependencies:
+    "@types/prop-types": "*"
+    "@types/scheduler": "*"
+    csstype: ^3.0.2
+  checksum: 3c5373845b0869d9ce3db16917c87707cdfdb6c08d9ca7ed90a2a097cb3cbec8d037d37e81c3cf5af721d92f8fdc62431405f1874f9a02dea6ec05e5d81e77fe
+  languageName: node
+  linkType: hard
+
 "@types/resolve@npm:1.17.1":
   version: 1.17.1
   resolution: "@types/resolve@npm:1.17.1"
@@ -14451,7 +14487,11 @@ __metadata:
     "@testing-library/react": ^12.0.0
     "@testing-library/react-hooks": ^3.4.2
     "@testing-library/react-native": ^7.1.0
-    "@types/react-redux": ^7.1.16
+    "@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