From b50f392fd1dcca07720e99c2be5c6294da859ab7 Mon Sep 17 00:00:00 2001 From: Oliver Joseph Ash Date: Thu, 6 Oct 2022 14:19:25 +0100 Subject: [PATCH 01/10] `Provider`: pass state (`S`) generic through to `ProviderProps` This is necessary to catch mistakes such as this, where the type of `serverState` does not match the type of the state in the provided `store`. ```tsx import { Provider } from 'react-redux'; import { Store } from 'redux'; declare const store: Store<{ foo: string }>; foo ``` --- src/components/Provider.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/Provider.tsx b/src/components/Provider.tsx index 364ff7bc8..e3340355d 100644 --- a/src/components/Provider.tsx +++ b/src/components/Provider.tsx @@ -24,12 +24,12 @@ export interface ProviderProps { children: ReactNode } -function Provider({ +function Provider({ store, context, children, serverState, -}: ProviderProps) { +}: ProviderProps) { const contextValue = useMemo(() => { const subscription = createSubscription(store) return { From e7017ed983dc99876192fd0b6e0ca6e063d9f651 Mon Sep 17 00:00:00 2001 From: Oliver Joseph Ash Date: Thu, 6 Oct 2022 16:33:09 +0100 Subject: [PATCH 02/10] Add type test --- test/typetests/provider.tsx | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 test/typetests/provider.tsx diff --git a/test/typetests/provider.tsx b/test/typetests/provider.tsx new file mode 100644 index 000000000..f6cbc263c --- /dev/null +++ b/test/typetests/provider.tsx @@ -0,0 +1,19 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ + +import React from 'react' +import { Provider } from '../../src' +import { Store } from 'redux' + +declare const store: Store<{ foo: string }> + +function App() { + return ( + + foo + + ) +} From 9e09869307f829273d8a38a452d814a546b692b0 Mon Sep 17 00:00:00 2001 From: Oliver Joseph Ash Date: Thu, 6 Oct 2022 16:34:13 +0100 Subject: [PATCH 03/10] `any` -> `unknown` --- src/components/Provider.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/Provider.tsx b/src/components/Provider.tsx index e3340355d..f3ffb2da1 100644 --- a/src/components/Provider.tsx +++ b/src/components/Provider.tsx @@ -4,7 +4,7 @@ import { createSubscription } from '../utils/Subscription' import { useIsomorphicLayoutEffect } from '../utils/useIsomorphicLayoutEffect' import { Action, AnyAction, Store } from 'redux' -export interface ProviderProps { +export interface ProviderProps { /** * The single Redux store in your application. */ @@ -24,7 +24,7 @@ export interface ProviderProps { children: ReactNode } -function Provider({ +function Provider({ store, context, children, From 54d80967e2dc53205d02369273bde94040f0276d Mon Sep 17 00:00:00 2001 From: Enger Then Date: Fri, 28 Oct 2022 11:40:31 -0400 Subject: [PATCH 04/10] chore(docs): added custom equalityFn example --- docs/api/hooks.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/docs/api/hooks.md b/docs/api/hooks.md index db8d20840..337354ac2 100644 --- a/docs/api/hooks.md +++ b/docs/api/hooks.md @@ -100,6 +100,18 @@ import { shallowEqual, useSelector } from 'react-redux' const selectedData = useSelector(selectorReturningObject, shallowEqual) ``` +- Use a custom equality function as the `equalityFn` argument to `useSelector()`, like: + +```js +import { useSelector } from 'react-redux' + +// equality function +const customEqual = (oldValue, newValue) => oldValue === newValue + +// later +const selectedData = useSelector(selectorReturningObject, customEqual) +``` + The optional comparison function also enables using something like Lodash's `_.isEqual()` or Immutable.js's comparison capabilities. ### `useSelector` Examples From 3b0606109ab5463281c4c50696932b393f44ab08 Mon Sep 17 00:00:00 2001 From: Lenz Weber Date: Tue, 25 Oct 2022 12:04:48 +0000 Subject: [PATCH 05/10] wrap type in --- src/hooks/useSelector.ts | 4 ++-- src/types.ts | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/hooks/useSelector.ts b/src/hooks/useSelector.ts index 034b2d216..626b0b483 100644 --- a/src/hooks/useSelector.ts +++ b/src/hooks/useSelector.ts @@ -2,7 +2,7 @@ import { useContext, useDebugValue } from 'react' import { useReduxContext as useDefaultReduxContext } from './useReduxContext' import { ReactReduxContext } from '../components/Context' -import type { EqualityFn } from '../types' +import type { EqualityFn, NoInfer } from '../types' import type { uSESWS } from '../utils/useSyncExternalStore' import { notInitialized } from '../utils/useSyncExternalStore' @@ -32,7 +32,7 @@ export function createSelectorHook( return function useSelector( selector: (state: TState) => Selected, - equalityFn: EqualityFn = refEquality + equalityFn: EqualityFn> = refEquality ): Selected { if (process.env.NODE_ENV !== 'production') { if (!selector) { diff --git a/src/types.ts b/src/types.ts index 5a8017d1e..4e424c994 100644 --- a/src/types.ts +++ b/src/types.ts @@ -168,3 +168,5 @@ export interface TypedUseSelectorHook { equalityFn?: EqualityFn ): TSelected } + +export type NoInfer = [T][T extends any ? 0 : never] From 401250ebee03435bdf23e8b55bf490fd56567438 Mon Sep 17 00:00:00 2001 From: Lenz Weber Date: Tue, 25 Oct 2022 19:54:11 +0000 Subject: [PATCH 06/10] also update TypedUseSelectorHook --- src/types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/types.ts b/src/types.ts index 4e424c994..90ecebe8d 100644 --- a/src/types.ts +++ b/src/types.ts @@ -165,7 +165,7 @@ export type ResolveThunks = TDispatchProps extends { export interface TypedUseSelectorHook { ( selector: (state: TState) => TSelected, - equalityFn?: EqualityFn + equalityFn?: EqualityFn> ): TSelected } From fc3954a680649d5aae74df03155147b363dafd40 Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Thu, 3 Nov 2022 22:03:44 -0400 Subject: [PATCH 07/10] Update test matrix to Node 16 and TS 4.9 --- .github/workflows/test.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f26313ce0..776d7d979 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -19,7 +19,7 @@ jobs: - name: Set up Node uses: actions/setup-node@v2 with: - node-version: 14.x + node-version: 16.x cache: 'yarn' - name: Install dependencies @@ -39,8 +39,8 @@ jobs: strategy: fail-fast: false matrix: - node: ['14.x'] - ts: ['4.0', '4.1', '4.2', '4.3', '4.4', '4.5', '4.6', 'next'] + node: ['16.x'] + ts: ['4.1', '4.2', '4.3', '4.4', '4.5', '4.6', '4.7', '4.8', '4.9.2-rc'] steps: - name: Checkout repo uses: actions/checkout@v2 From bafe55eab475589efc2c1aaee89bde244e2fb44e Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Thu, 3 Nov 2022 22:16:31 -0400 Subject: [PATCH 08/10] Add typetests --- test/typetests/hooks.tsx | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/test/typetests/hooks.tsx b/test/typetests/hooks.tsx index 84475e961..9bbce00e4 100644 --- a/test/typetests/hooks.tsx +++ b/test/typetests/hooks.tsx @@ -34,7 +34,7 @@ import { fetchCount, } from './counterApp' -import { expectType } from '../typeTestHelpers' +import { expectType, expectExactType } from '../typeTestHelpers' function preTypedHooksSetup() { // Standard hooks setup @@ -87,6 +87,20 @@ function testShallowEqual() { shallowEqual({ test: 'test' }, { test: 'test' }) shallowEqual({ test: 'test' }, 'a') const x: boolean = shallowEqual('a', 'a') + + type TestState = { stateProp: string } + + // Additionally, it should infer its type from arguments and not become `any` + const selected1 = useSelector( + (state: TestState) => state.stateProp, + shallowEqual + ) + expectExactType(selected1) + + const useAppSelector: TypedUseSelectorHook = useSelector + + const selected2 = useAppSelector((state) => state.stateProp, shallowEqual) + expectExactType(selected2) } function testUseDispatch() { From ff7d96befaf28e3d4975071161c51c5ec2e4cf06 Mon Sep 17 00:00:00 2001 From: Felipe Guizar Date: Wed, 22 Jun 2022 23:02:34 -0500 Subject: [PATCH 09/10] fix: type when passing nullish mapDispathToProps --- src/components/connect.tsx | 6 ++ .../connect-mapstate-mapdispatch.tsx | 61 +++++++++++++++++++ 2 files changed, 67 insertions(+) diff --git a/src/components/connect.tsx b/src/components/connect.tsx index a946a70db..5e318a5c4 100644 --- a/src/components/connect.tsx +++ b/src/components/connect.tsx @@ -305,6 +305,12 @@ export interface Connect { TOwnProps > + /** mapState and mapDispatch (nullish) */ + ( + mapStateToProps: MapStateToPropsParam, + mapDispatchToProps: null | undefined + ): InferableComponentEnhancerWithProps + /** mapState and mapDispatch (as an object) */ ( mapStateToProps: MapStateToPropsParam, diff --git a/test/typetests/connect-mapstate-mapdispatch.tsx b/test/typetests/connect-mapstate-mapdispatch.tsx index 946f62181..549119d0e 100644 --- a/test/typetests/connect-mapstate-mapdispatch.tsx +++ b/test/typetests/connect-mapstate-mapdispatch.tsx @@ -269,6 +269,40 @@ function MapStateAndDispatchObject() { const verify = } +function MapStateAndNullishDispatch() { + interface ClickPayload { + count: number + } + const onClick: ActionCreator = () => ({ count: 1 }) + const dispatchToProps = { + onClick, + } + + interface OwnProps { + foo: string + } + interface StateProps { + bar: number + } + + const mapStateToProps = (_: any, __: OwnProps): StateProps => ({ + bar: 1, + }) + + class TestComponent extends React.Component {} + + const TestDispatchPropsNull = connect(mapStateToProps, null)(TestComponent) + + const verifyNull = + + const TestDispatchPropsUndefined = connect( + mapStateToProps, + undefined + )(TestComponent) + + const verifyNonUn = +} + function MapDispatchFactory() { interface OwnProps { foo: string @@ -422,6 +456,33 @@ function MapStateAndDispatchAndMerge() { const verify = } +function MapStateAndMerge() { + interface OwnProps { + foo: string + } + interface StateProps { + bar: number + } + interface DispatchProps { + onClick: () => void + } + + class TestComponent extends React.Component {} + + const mapStateToProps = () => ({ + bar: 1, + }) + + const mergeProps = (stateProps: StateProps, _: null, ownProps: OwnProps) => ({ + ...stateProps, + ...ownProps, + }) + + const Test = connect(mapStateToProps, null, mergeProps)(TestComponent) + + const verify = +} + function MapStateAndOptions() { interface State { state: string From 8d03182d36abe91cb0cc883478f3b0c2d7f9e17f Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Thu, 3 Nov 2022 22:40:26 -0400 Subject: [PATCH 10/10] Release 8.0.5 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 28e094f93..ed2508384 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-redux", - "version": "8.0.4", + "version": "8.0.5", "description": "Official React bindings for Redux", "keywords": [ "react",