Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rework connect types and add type test setup #1766

Merged
merged 13 commits into from Jul 9, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 5 additions & 1 deletion .eslintrc
Expand Up @@ -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": {
Expand Down
54 changes: 54 additions & 0 deletions .github/workflows/test.yml
Expand Up @@ -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
2 changes: 2 additions & 0 deletions .gitignore
Expand Up @@ -3,6 +3,8 @@ dist
lib
coverage
es
temp/
react-redux-*/

.cache
.yarnrc
Expand Down
59 changes: 26 additions & 33 deletions etc/react-redux.api.md
Expand Up @@ -4,7 +4,6 @@

```ts

/// <reference types="hoist-non-react-statics" />
/// <reference types="react" />

import { Action } from 'redux';
Expand All @@ -15,49 +14,40 @@ 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;

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 {
Expand All @@ -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)
Expand Down
9 changes: 7 additions & 2 deletions package.json
Expand Up @@ -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": [
Expand All @@ -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",
Expand All @@ -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",
Expand Down
58 changes: 32 additions & 26 deletions 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,
Expand All @@ -31,7 +26,7 @@ const stringifyComponent = (Comp: unknown) => {
}

function storeStateUpdatesReducer(
state: [payload: unknown, counter: number],
state: [unknown, number],
action: { payload: unknown }
) {
const [, updateCount] = state
Expand Down Expand Up @@ -108,7 +103,7 @@ function subscribeUpdates(
)
} catch (e) {
error = e
lastThrownError = e
lastThrownError = e as Error | null
}

if (!error) {
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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

Expand All @@ -492,17 +495,20 @@ 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)
}

return hoistStatics(Connect, WrappedComponent)
}

return wrapWithConnect
}

export default connectAdvanced