diff --git a/rollup.config.js b/rollup.config.js index f75eb672d..2f8d004ff 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -10,7 +10,7 @@ const env = process.env.NODE_ENV const extensions = ['.js', '.ts', '.tsx', '.json'] const config = { - input: 'src/index.js', + input: 'src/index.ts', external: Object.keys(pkg.peerDependencies || {}).concat('react-dom'), output: { format: 'umd', diff --git a/src/components/Provider.tsx b/src/components/Provider.tsx index 32f100994..90a04b83e 100644 --- a/src/components/Provider.tsx +++ b/src/components/Provider.tsx @@ -5,7 +5,7 @@ import { useIsomorphicLayoutEffect } from '../utils/useIsomorphicLayoutEffect' import type { FixTypeLater } from '../types' import { Action, AnyAction, Store } from 'redux' -interface ProviderProps { +export interface ProviderProps { /** * The single Redux store in your application. */ diff --git a/src/components/connectAdvanced.tsx b/src/components/connectAdvanced.tsx index cd645cc91..56bcd65e2 100644 --- a/src/components/connectAdvanced.tsx +++ b/src/components/connectAdvanced.tsx @@ -173,14 +173,7 @@ export interface ConnectProps { store?: Store } -export type ConnectedComponent< - C extends React.ComponentType, - P -> = React.NamedExoticComponent> & { - WrappedComponent: C -} - -interface ConnectAdvancedOptions { +export interface ConnectAdvancedOptions { getDisplayName?: (name: string) => string methodName?: string shouldHandleStateChanges?: boolean @@ -189,7 +182,16 @@ interface ConnectAdvancedOptions { pure?: boolean } -export default function connectAdvanced( +interface AnyObject { + [x: string]: any +} + +export default function connectAdvanced< + S, + TProps, + TOwnProps, + TFactoryOptions extends AnyObject = {} +>( /* selectorFactory is a func that is responsible for returning the selector function used to compute new props from state, props, and dispatch. For example: @@ -207,7 +209,7 @@ export default function connectAdvanced( props. Do not use connectAdvanced directly without memoizing results between calls to your selector, otherwise the Connect component will re-render on every state or props change. */ - selectorFactory: SelectorFactory, + selectorFactory: SelectorFactory, // options object: { // the func used to compute this HOC's displayName from the wrapped component's displayName. @@ -229,7 +231,7 @@ export default function connectAdvanced( // additional options are passed through to the selectorFactory ...connectOptions - }: ConnectAdvancedOptions = {} + }: ConnectAdvancedOptions & Partial = {} ) { const Context = context diff --git a/src/connect/connect.ts b/src/connect/connect.ts index c341ddb2c..d83513b2f 100644 --- a/src/connect/connect.ts +++ b/src/connect/connect.ts @@ -1,5 +1,6 @@ import type { Dispatch } from 'redux' import connectAdvanced from '../components/connectAdvanced' +import type { ConnectAdvancedOptions } from '../components/connectAdvanced' import shallowEqual from '../utils/shallowEqual' import defaultMapDispatchToPropsFactories from './mapDispatchToProps' import defaultMapStateToPropsFactories from './mapStateToProps' @@ -9,6 +10,7 @@ import defaultSelectorFactory, { MapDispatchToPropsParam, MergeProps, } from './selectorFactory' +import type { DefaultRootState } from '../types' /* connect is a facade over connectAdvanced. It turns its args into a compatible @@ -50,6 +52,31 @@ function strictEqual(a: unknown, b: unknown) { return a === b } +export interface ConnectOptions< + State = DefaultRootState, + TStateProps = {}, + TOwnProps = {}, + TMergedProps = {} +> extends ConnectAdvancedOptions { + pure?: boolean | undefined + areStatesEqual?: ((nextState: State, prevState: State) => boolean) | undefined + + areOwnPropsEqual?: ( + nextOwnProps: TOwnProps, + prevOwnProps: TOwnProps + ) => boolean + + areStatePropsEqual?: ( + nextStateProps: TStateProps, + prevStateProps: TStateProps + ) => boolean + areMergedPropsEqual?: ( + nextMergedProps: TMergedProps, + prevMergedProps: TMergedProps + ) => boolean + forwardRef?: boolean | undefined +} + // createConnect with default args builds the 'official' connect behavior. Calling it with // different options opens up some testing and extensibility scenarios export function createConnect({ @@ -70,7 +97,7 @@ export function createConnect({ areStatePropsEqual = shallowEqual, areMergedPropsEqual = shallowEqual, ...extraOptions - } = {} + }: ConnectOptions = {} ) { const initMapStateToProps = match( mapStateToProps, diff --git a/src/index.js b/src/index.js deleted file mode 100644 index 27254b6e8..000000000 --- a/src/index.js +++ /dev/null @@ -1,29 +0,0 @@ -import Provider from './components/Provider' -import connectAdvanced from './components/connectAdvanced' -import { ReactReduxContext } from './components/Context' -import connect from './connect/connect' - -import { useDispatch, createDispatchHook } from './hooks/useDispatch' -import { useSelector, createSelectorHook } from './hooks/useSelector' -import { useStore, createStoreHook } from './hooks/useStore' - -import { setBatch } from './utils/batch' -import { unstable_batchedUpdates as batch } from './utils/reactBatchedUpdates' -import shallowEqual from './utils/shallowEqual' - -setBatch(batch) - -export { - Provider, - connectAdvanced, - ReactReduxContext, - connect, - batch, - useDispatch, - createDispatchHook, - useSelector, - createSelectorHook, - useStore, - createStoreHook, - shallowEqual, -} diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 000000000..1073f81e2 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,66 @@ +import Provider from './components/Provider' +import type { ProviderProps } from './components/Provider' +import connectAdvanced from './components/connectAdvanced' +import type { + ConnectAdvancedOptions, + ConnectProps, +} from './components/connectAdvanced' +import type { + SelectorFactory, + Selector, + MapStateToProps, + MapStateToPropsFactory, + MapStateToPropsParam, + MapDispatchToPropsFunction, + MapDispatchToProps, + MapDispatchToPropsFactory, + MapDispatchToPropsParam, + MapDispatchToPropsNonObject, + MergeProps, +} from './connect/selectorFactory' +import { ReactReduxContext } from './components/Context' +import type { ReactReduxContextValue } from './components/Context' +import connect from './connect/connect' + +import { useDispatch, createDispatchHook } from './hooks/useDispatch' +import { useSelector, createSelectorHook } from './hooks/useSelector' +import { useStore, createStoreHook } from './hooks/useStore' + +import { setBatch } from './utils/batch' +import { unstable_batchedUpdates as batch } from './utils/reactBatchedUpdates' +import shallowEqual from './utils/shallowEqual' + +setBatch(batch) + +export * from './types' +export type { + ProviderProps, + SelectorFactory, + Selector, + MapStateToProps, + MapStateToPropsFactory, + MapStateToPropsParam, + ConnectProps, + ConnectAdvancedOptions, + MapDispatchToPropsFunction, + MapDispatchToProps, + MapDispatchToPropsFactory, + MapDispatchToPropsParam, + MapDispatchToPropsNonObject, + MergeProps, + ReactReduxContextValue, +} +export { + Provider, + connectAdvanced, + ReactReduxContext, + connect, + batch, + useDispatch, + createDispatchHook, + useSelector, + createSelectorHook, + useStore, + createStoreHook, + shallowEqual, +} diff --git a/src/types.ts b/src/types.ts index 6b436517d..dc2a91f48 100644 --- a/src/types.ts +++ b/src/types.ts @@ -256,3 +256,22 @@ export type ResolveArrayThunks> = : TDispatchProps extends ReadonlyArray ? ReadonlyArray> : never + +/** + * This interface allows you to easily create a hook that is properly typed for your + * store's root state. + * + * @example + * + * interface RootState { + * property: string; + * } + * + * const useTypedSelector: TypedUseSelectorHook = useSelector; + */ +export interface TypedUseSelectorHook { + ( + selector: (state: TState) => TSelected, + equalityFn?: (left: TSelected, right: TSelected) => boolean + ): TSelected +} diff --git a/test/components/Provider.spec.js b/test/components/Provider.spec.js index f4a3987df..116570a1e 100644 --- a/test/components/Provider.spec.js +++ b/test/components/Provider.spec.js @@ -3,11 +3,14 @@ import React, { Component } from 'react' import ReactDOM from 'react-dom' import { createStore } from 'redux' -import { Provider, connect, ReactReduxContext } from '../../src/index.js' +import { Provider, connect, ReactReduxContext } from '../../src/index' import * as rtl from '@testing-library/react' import '@testing-library/jest-dom/extend-expect' -const createExampleTextReducer = () => (state = 'example text') => state +const createExampleTextReducer = + () => + (state = 'example text') => + state describe('React', () => { describe('Provider', () => { diff --git a/test/components/connect.spec.js b/test/components/connect.spec.js index 212b0a8ec..765596794 100644 --- a/test/components/connect.spec.js +++ b/test/components/connect.spec.js @@ -5,7 +5,7 @@ import createClass from 'create-react-class' import PropTypes from 'prop-types' import ReactDOM from 'react-dom' import { createStore, applyMiddleware } from 'redux' -import { Provider as ProviderMock, connect } from '../../src/index.js' +import { Provider as ProviderMock, connect } from '../../src/index' import * as rtl from '@testing-library/react' import '@testing-library/jest-dom/extend-expect' diff --git a/test/components/connectAdvanced.spec.js b/test/components/connectAdvanced.spec.js index 4b0f8bdab..4df5df798 100644 --- a/test/components/connectAdvanced.spec.js +++ b/test/components/connectAdvanced.spec.js @@ -1,6 +1,6 @@ import React, { Component } from 'react' import * as rtl from '@testing-library/react' -import { Provider as ProviderMock, connectAdvanced } from '../../src/index.js' +import { Provider as ProviderMock, connectAdvanced } from '../../src/index' import { createStore } from 'redux' import '@testing-library/jest-dom/extend-expect' diff --git a/test/components/hooks.spec.js b/test/components/hooks.spec.js index 44a797210..ece7250ea 100644 --- a/test/components/hooks.spec.js +++ b/test/components/hooks.spec.js @@ -2,7 +2,7 @@ import React from 'react' import { createStore } from 'redux' -import { Provider as ProviderMock, connect } from '../../src/index.js' +import { Provider as ProviderMock, connect } from '../../src/index' import * as rtl from '@testing-library/react' import '@testing-library/jest-dom/extend-expect' diff --git a/test/hooks/useDispatch.spec.js b/test/hooks/useDispatch.spec.js index de3dbe46e..9473cc791 100644 --- a/test/hooks/useDispatch.spec.js +++ b/test/hooks/useDispatch.spec.js @@ -5,7 +5,7 @@ import { Provider as ProviderMock, useDispatch, createDispatchHook, -} from '../../src/index.js' +} from '../../src/index' const store = createStore((c) => c + 1) const store2 = createStore((c) => c + 2) diff --git a/test/hooks/useSelector.spec.js b/test/hooks/useSelector.spec.js index 2f927214e..ab2a29738 100644 --- a/test/hooks/useSelector.spec.js +++ b/test/hooks/useSelector.spec.js @@ -10,7 +10,7 @@ import { shallowEqual, connect, createSelectorHook, -} from '../../src/index.js' +} from '../../src/index' import { useReduxContext } from '../../src/hooks/useReduxContext' describe('React', () => { diff --git a/test/integration/dynamic-reducers.spec.js b/test/integration/dynamic-reducers.spec.js index 841b0377a..dc3e8addf 100644 --- a/test/integration/dynamic-reducers.spec.js +++ b/test/integration/dynamic-reducers.spec.js @@ -3,7 +3,7 @@ import React from 'react' import ReactDOMServer from 'react-dom/server' import { createStore, combineReducers } from 'redux' -import { connect, Provider, ReactReduxContext } from '../../src/index.js' +import { connect, Provider, ReactReduxContext } from '../../src/index' import * as rtl from '@testing-library/react' describe('React', () => { diff --git a/test/integration/server-rendering.spec.js b/test/integration/server-rendering.spec.js index a957907f7..7d92ebf99 100644 --- a/test/integration/server-rendering.spec.js +++ b/test/integration/server-rendering.spec.js @@ -11,7 +11,7 @@ import React from 'react' import { renderToString } from 'react-dom/server' import { createStore } from 'redux' -import { Provider, connect } from '../../src/index.js' +import { Provider, connect } from '../../src/index' describe('React', () => { describe('server rendering', () => { diff --git a/test/react-native/batch-integration.js b/test/react-native/batch-integration.js index e5b8586d4..bc28f51ab 100644 --- a/test/react-native/batch-integration.js +++ b/test/react-native/batch-integration.js @@ -7,7 +7,7 @@ import { batch, useSelector, useDispatch, -} from '../../src/index.js' +} from '../../src/index' import { useIsomorphicLayoutEffect } from '../../src/utils/useIsomorphicLayoutEffect' import * as rtl from '@testing-library/react-native' import '@testing-library/jest-native/extend-expect' @@ -468,9 +468,8 @@ describe('React Native', () => { const rendered = rtl.render() const assertValuesMatch = (rendered) => { - const [, boolFromSelector] = rendered.getByTestId( - 'boolFromSelector' - ).children + const [, boolFromSelector] = + rendered.getByTestId('boolFromSelector').children const [, boolFromStore] = rendered.getByTestId('boolFromStore').children expect(boolFromSelector).toBe(boolFromStore) }