1
1
import hoistStatics from 'hoist-non-react-statics'
2
- import React , { useContext , useMemo , useRef , useReducer } from 'react'
2
+ import React , {
3
+ useContext ,
4
+ useMemo ,
5
+ useRef ,
6
+ useReducer ,
7
+ useLayoutEffect ,
8
+ } from 'react'
3
9
import { isValidElementType , isContextConsumer } from 'react-is'
4
- import { createSubscription } from '../utils/Subscription'
10
+ import type { Store } from 'redux'
11
+ import type { SelectorFactory } from '../connect/selectorFactory'
12
+ import { createSubscription , Subscription } from '../utils/Subscription'
5
13
import { useIsomorphicLayoutEffect } from '../utils/useIsomorphicLayoutEffect'
6
14
7
- import { ReactReduxContext } from './Context'
15
+ import {
16
+ ReactReduxContext ,
17
+ ReactReduxContextValue ,
18
+ ReactReduxContextInstance ,
19
+ } from './Context'
8
20
9
21
// Define some constant arrays just to avoid re-creating these
10
- const EMPTY_ARRAY = [ ]
22
+ const EMPTY_ARRAY : [ unknown , number ] = [ null , 0 ]
11
23
const NO_SUBSCRIPTION_ARRAY = [ null , null ]
12
24
13
- const stringifyComponent = ( Comp ) => {
25
+ const stringifyComponent = ( Comp : unknown ) => {
14
26
try {
15
27
return JSON . stringify ( Comp )
16
28
} catch ( err ) {
17
29
return String ( Comp )
18
30
}
19
31
}
20
32
21
- function storeStateUpdatesReducer ( state , action ) {
33
+ function storeStateUpdatesReducer (
34
+ state : [ payload : unknown , counter : number ] ,
35
+ action : { payload : unknown }
36
+ ) {
22
37
const [ , updateCount ] = state
23
38
return [ action . payload , updateCount + 1 ]
24
39
}
25
40
41
+ type EffectFunc = ( ...args : any [ ] ) => void | ReturnType < React . EffectCallback >
42
+
26
43
function useIsomorphicLayoutEffectWithArgs (
27
- effectFunc ,
28
- effectArgs ,
29
- dependencies
44
+ effectFunc : EffectFunc ,
45
+ effectArgs : any [ ] ,
46
+ dependencies ?: React . DependencyList
30
47
) {
31
48
useIsomorphicLayoutEffect ( ( ) => effectFunc ( ...effectArgs ) , dependencies )
32
49
}
33
50
34
51
function captureWrapperProps (
35
- lastWrapperProps ,
36
- lastChildProps ,
37
- renderIsScheduled ,
38
- wrapperProps ,
39
- actualChildProps ,
40
- childPropsFromStoreUpdate ,
41
- notifyNestedSubs
52
+ lastWrapperProps : React . MutableRefObject < unknown > ,
53
+ lastChildProps : React . MutableRefObject < unknown > ,
54
+ renderIsScheduled : React . MutableRefObject < boolean > ,
55
+ wrapperProps : React . MutableRefObject < unknown > ,
56
+ actualChildProps : React . MutableRefObject < unknown > ,
57
+ childPropsFromStoreUpdate : React . MutableRefObject < unknown > ,
58
+ notifyNestedSubs : ( ) => void
42
59
) {
43
60
// We want to capture the wrapper props and child props we used for later comparisons
44
61
lastWrapperProps . current = wrapperProps
@@ -53,23 +70,23 @@ function captureWrapperProps(
53
70
}
54
71
55
72
function subscribeUpdates (
56
- shouldHandleStateChanges ,
57
- store ,
58
- subscription ,
59
- childPropsSelector ,
60
- lastWrapperProps ,
61
- lastChildProps ,
62
- renderIsScheduled ,
63
- childPropsFromStoreUpdate ,
64
- notifyNestedSubs ,
65
- forceComponentUpdateDispatch
73
+ shouldHandleStateChanges : boolean ,
74
+ store : Store ,
75
+ subscription : Subscription ,
76
+ childPropsSelector : ( state : unknown , props : unknown ) => unknown ,
77
+ lastWrapperProps : React . MutableRefObject < unknown > ,
78
+ lastChildProps : React . MutableRefObject < unknown > ,
79
+ renderIsScheduled : React . MutableRefObject < boolean > ,
80
+ childPropsFromStoreUpdate : React . MutableRefObject < unknown > ,
81
+ notifyNestedSubs : ( ) => void ,
82
+ forceComponentUpdateDispatch : React . Dispatch < any >
66
83
) {
67
84
// If we're not subscribed to the store, nothing to do here
68
85
if ( ! shouldHandleStateChanges ) return
69
86
70
87
// Capture values for checking if and when this component unmounts
71
88
let didUnsubscribe = false
72
- let lastThrownError = null
89
+ let lastThrownError : Error | null = null
73
90
74
91
// We'll run this callback every time a store subscription update propagates to this component
75
92
const checkForUpdates = ( ) => {
@@ -148,7 +165,29 @@ function subscribeUpdates(
148
165
return unsubscribeWrapper
149
166
}
150
167
151
- const initStateUpdates = ( ) => [ null , 0 ]
168
+ const initStateUpdates = ( ) => EMPTY_ARRAY
169
+
170
+ export interface ConnectProps {
171
+ reactReduxForwardedRef ?: React . ForwardedRef < unknown >
172
+ context ?: ReactReduxContextInstance
173
+ store ?: Store
174
+ }
175
+
176
+ export type ConnectedComponent <
177
+ C extends React . ComponentType < any > ,
178
+ P
179
+ > = React . NamedExoticComponent < JSX . LibraryManagedAttributes < C , P > > & {
180
+ WrappedComponent : C
181
+ }
182
+
183
+ interface ConnectAdvancedOptions {
184
+ getDisplayName ?: ( name : string ) => string
185
+ methodName ?: string
186
+ shouldHandleStateChanges ?: boolean
187
+ forwardRef ?: boolean
188
+ context ?: typeof ReactReduxContext
189
+ pure ?: boolean
190
+ }
152
191
153
192
export default function connectAdvanced (
154
193
/*
@@ -168,7 +207,7 @@ export default function connectAdvanced(
168
207
props. Do not use connectAdvanced directly without memoizing results between calls to your
169
208
selector, otherwise the Connect component will re-render on every state or props change.
170
209
*/
171
- selectorFactory ,
210
+ selectorFactory : SelectorFactory < unknown , unknown , unknown , unknown > ,
172
211
// options object:
173
212
{
174
213
// the func used to compute this HOC's displayName from the wrapped component's displayName.
@@ -179,19 +218,9 @@ export default function connectAdvanced(
179
218
// probably overridden by wrapper functions such as connect()
180
219
methodName = 'connectAdvanced' ,
181
220
182
- // REMOVED: if defined, the name of the property passed to the wrapped element indicating the number of
183
- // calls to render. useful for watching in react devtools for unnecessary re-renders.
184
- renderCountProp = undefined ,
185
-
186
221
// determines whether this HOC subscribes to store changes
187
222
shouldHandleStateChanges = true ,
188
223
189
- // REMOVED: the key of props/context to get the store
190
- storeKey = 'store' ,
191
-
192
- // REMOVED: expose the wrapped component via refs
193
- withRef = false ,
194
-
195
224
// use React's forwardRef to expose a ref of the wrapped component
196
225
forwardRef = false ,
197
226
@@ -200,37 +229,13 @@ export default function connectAdvanced(
200
229
201
230
// additional options are passed through to the selectorFactory
202
231
...connectOptions
203
- } = { }
232
+ } : ConnectAdvancedOptions = { }
204
233
) {
205
- if ( process . env . NODE_ENV !== 'production' ) {
206
- if ( renderCountProp !== undefined ) {
207
- throw new Error (
208
- `renderCountProp is removed. render counting is built into the latest React Dev Tools profiling extension`
209
- )
210
- }
211
- if ( withRef ) {
212
- throw new Error (
213
- 'withRef is removed. To access the wrapped instance, use a ref on the connected component'
214
- )
215
- }
216
-
217
- const customStoreWarningMessage =
218
- 'To use a custom Redux store for specific components, create a custom React context with ' +
219
- "React.createContext(), and pass the context object to React Redux's Provider and specific components" +
220
- ' like: <Provider context={MyContext}><ConnectedComponent context={MyContext} /></Provider>. ' +
221
- 'You may also pass a {context : MyContext} option to connect'
222
-
223
- if ( storeKey !== 'store' ) {
224
- throw new Error (
225
- 'storeKey has been removed and does not do anything. ' +
226
- customStoreWarningMessage
227
- )
228
- }
229
- }
230
-
231
234
const Context = context
232
235
233
- return function wrapWithConnect ( WrappedComponent ) {
236
+ return function wrapWithConnect < WC extends React . ComponentType > (
237
+ WrappedComponent : WC
238
+ ) {
234
239
if (
235
240
process . env . NODE_ENV !== 'production' &&
236
241
! isValidElementType ( WrappedComponent )
@@ -252,43 +257,41 @@ export default function connectAdvanced(
252
257
...connectOptions ,
253
258
getDisplayName,
254
259
methodName,
255
- renderCountProp,
256
260
shouldHandleStateChanges,
257
- storeKey,
258
261
displayName,
259
262
wrappedComponentName,
260
263
WrappedComponent,
261
264
}
262
265
263
266
const { pure } = connectOptions
264
267
265
- function createChildSelector ( store ) {
268
+ function createChildSelector ( store : Store ) {
266
269
return selectorFactory ( store . dispatch , selectorFactoryOptions )
267
270
}
268
271
269
272
// If we aren't running in "pure" mode, we don't want to memoize values.
270
273
// To avoid conditionally calling hooks, we fall back to a tiny wrapper
271
274
// that just executes the given callback immediately.
272
- const usePureOnlyMemo = pure ? useMemo : ( callback ) => callback ( )
273
-
274
- function ConnectFunction ( props ) {
275
- const [
276
- propsContext ,
277
- reactReduxForwardedRef ,
278
- wrapperProps ,
279
- ] = useMemo ( ( ) => {
280
- // Distinguish between actual "data" props that were passed to the wrapper component,
281
- // and values needed to control behavior (forwarded refs, alternate context instances).
282
- // To maintain the wrapperProps object reference, memoize this destructuring.
283
- const { reactReduxForwardedRef, ...wrapperProps } = props
284
- return [ props . context , reactReduxForwardedRef , wrapperProps ]
285
- } , [ props ] )
286
-
287
- const ContextToUse = useMemo ( ( ) => {
275
+ const usePureOnlyMemo = pure
276
+ ? useMemo
277
+ : ( callback : ( ) => void ) => callback ( )
278
+
279
+ function ConnectFunction < TOwnProps > ( props : ConnectProps & TOwnProps ) {
280
+ const [ propsContext , reactReduxForwardedRef , wrapperProps ] =
281
+ useMemo ( ( ) => {
282
+ // Distinguish between actual "data" props that were passed to the wrapper component,
283
+ // and values needed to control behavior (forwarded refs, alternate context instances).
284
+ // To maintain the wrapperProps object reference, memoize this destructuring.
285
+ const { reactReduxForwardedRef, ...wrapperProps } = props
286
+ return [ props . context , reactReduxForwardedRef , wrapperProps ]
287
+ } , [ props ] )
288
+
289
+ const ContextToUse : ReactReduxContextInstance = useMemo ( ( ) => {
288
290
// Users may optionally pass in a custom context instance to use instead of our ReactReduxContext.
289
291
// Memoize the check that determines which context instance we should use.
290
292
return propsContext &&
291
293
propsContext . Consumer &&
294
+ // @ts -ignore
292
295
isContextConsumer ( < propsContext . Consumer /> )
293
296
? propsContext
294
297
: Context
@@ -302,10 +305,10 @@ export default function connectAdvanced(
302
305
// This allows us to pass through a `store` prop that is just a plain value.
303
306
const didStoreComeFromProps =
304
307
Boolean ( props . store ) &&
305
- Boolean ( props . store . getState ) &&
306
- Boolean ( props . store . dispatch )
308
+ Boolean ( props . store ! . getState ) &&
309
+ Boolean ( props . store ! . dispatch )
307
310
const didStoreComeFromContext =
308
- Boolean ( contextValue ) && Boolean ( contextValue . store )
311
+ Boolean ( contextValue ) && Boolean ( contextValue ! . store )
309
312
310
313
if (
311
314
process . env . NODE_ENV !== 'production' &&
@@ -321,7 +324,9 @@ export default function connectAdvanced(
321
324
}
322
325
323
326
// Based on the previous check, one of these must be true
324
- const store = didStoreComeFromProps ? props . store : contextValue . store
327
+ const store : Store = didStoreComeFromProps
328
+ ? props . store !
329
+ : contextValue ! . store
325
330
326
331
const childPropsSelector = useMemo ( ( ) => {
327
332
// The child props selector needs the store reference as an input.
@@ -336,7 +341,7 @@ export default function connectAdvanced(
336
341
// connected to the store via props shouldn't use subscription from context, or vice versa.
337
342
const subscription = createSubscription (
338
343
store ,
339
- didStoreComeFromProps ? null : contextValue . subscription
344
+ didStoreComeFromProps ? undefined : contextValue ! . subscription
340
345
)
341
346
342
347
// `notifyNestedSubs` is duplicated to handle the case where the component is unmounted in
@@ -356,23 +361,26 @@ export default function connectAdvanced(
356
361
// This component is directly subscribed to a store from props.
357
362
// We don't want descendants reading from this store - pass down whatever
358
363
// the existing context value is from the nearest connected ancestor.
359
- return contextValue
364
+ return contextValue !
360
365
}
361
366
362
367
// Otherwise, put this component's subscription instance into context, so that
363
368
// connected descendants won't update until after this component is done
364
369
return {
365
370
...contextValue ,
366
371
subscription,
367
- }
372
+ } as ReactReduxContextValue
368
373
} , [ didStoreComeFromProps , contextValue , subscription ] )
369
374
370
375
// We need to force this wrapper component to re-render whenever a Redux store update
371
376
// causes a change to the calculated child component props (or we caught an error in mapState)
372
- const [
373
- [ previousStateUpdateResult ] ,
374
- forceComponentUpdateDispatch ,
375
- ] = useReducer ( storeStateUpdatesReducer , EMPTY_ARRAY , initStateUpdates )
377
+ const [ [ previousStateUpdateResult ] , forceComponentUpdateDispatch ] =
378
+ useReducer (
379
+ storeStateUpdatesReducer ,
380
+ // @ts -ignore
381
+ EMPTY_ARRAY as any ,
382
+ initStateUpdates
383
+ )
376
384
377
385
// Propagate any mapState/mapDispatch errors upwards
378
386
if ( previousStateUpdateResult && previousStateUpdateResult . error ) {
@@ -441,6 +449,7 @@ export default function connectAdvanced(
441
449
// We memoize the elements for the rendered child component as an optimization.
442
450
const renderedWrappedComponent = useMemo (
443
451
( ) => (
452
+ // @ts -ignore
444
453
< WrappedComponent
445
454
{ ...actualChildProps }
446
455
ref = { reactReduxForwardedRef }
@@ -470,19 +479,23 @@ export default function connectAdvanced(
470
479
}
471
480
472
481
// If we're in "pure" mode, ensure our wrapper component only re-renders when incoming props have changed.
473
- const Connect = pure ? React . memo ( ConnectFunction ) : ConnectFunction
482
+ const _Connect = pure ? React . memo ( ConnectFunction ) : ConnectFunction
474
483
484
+ const Connect = _Connect as typeof _Connect & { WrappedComponent : WC }
475
485
Connect . WrappedComponent = WrappedComponent
476
486
Connect . displayName = ConnectFunction . displayName = displayName
477
487
478
488
if ( forwardRef ) {
479
- const forwarded = React . forwardRef ( function forwardConnectRef (
489
+ const _forwarded = React . forwardRef ( function forwardConnectRef (
480
490
props ,
481
491
ref
482
492
) {
483
493
return < Connect { ...props } reactReduxForwardedRef = { ref } />
484
494
} )
485
495
496
+ const forwarded = _forwarded as typeof _forwarded & {
497
+ WrappedComponent : WC
498
+ }
486
499
forwarded . displayName = displayName
487
500
forwarded . WrappedComponent = WrappedComponent
488
501
return hoistStatics ( forwarded , WrappedComponent )
0 commit comments