1
1
import { useEffect , useRef , useState } from "react" ;
2
- import {
2
+ import {
3
3
Action ,
4
+ ClientNotificationCallbackArgs ,
4
5
getContainer ,
6
+ getUniqueId ,
5
7
registerStateChangedCallback ,
6
8
TKnownStatePath ,
7
9
unregisterStateChangedCallback ,
8
- getUniqueId ,
9
10
} from "key-value-state-container" ;
10
11
import { useGetContainerId } from "./use-get-container-id" ;
11
12
import { RendersWithContainerId } from "./types/contracts" ;
12
13
13
- interface Args < TState extends Object > extends Partial < RendersWithContainerId > {
14
+ interface Args < TState extends Object , TAction extends Action = Action >
15
+ extends Partial < RendersWithContainerId > {
16
+ callback ?: ( args : ClientNotificationCallbackArgs < TState , TAction > ) => void ;
14
17
/**
15
18
* Diagnostics flag that helps to identify problems
16
19
* @default true
@@ -25,15 +28,40 @@ interface Args<TState extends Object> extends Partial<RendersWithContainerId> {
25
28
*/
26
29
lateInvoke ?: boolean ;
27
30
listenerTag ?: string ;
28
- statePath : TKnownStatePath < TState > [ ] ;
31
+
32
+ statePath : TKnownStatePath < TState > [ ] | TKnownStatePath < TState > ;
33
+
34
+ /**
35
+ * When `true`, selector code will be not active (will not register any callback)
36
+ * and always return the current container state (as it is), so the hook will no
37
+ * longer be a hook, but a simple function.
38
+ *
39
+ * Reason for introduction: it is not possible to have code that calls hooks
40
+ * conditionally, as this will violate rules of hooks, so this is a workaround.
41
+ * So, in other words, it is not possible to have something like this:
42
+ *
43
+ * ```
44
+ * if (hookNonNecessary) {
45
+ * return;
46
+ * }
47
+ * const result = useHook();
48
+ * ```
49
+ * The purpose is to makes code more linear and easier to maintain.
50
+ */
51
+ switchOff ?: boolean ;
29
52
}
30
53
31
- export const useSelector = < TState extends Object , TAction extends Action > ( {
54
+ export const useSelector = <
55
+ TState extends Object ,
56
+ TAction extends Action = Action
57
+ > ( {
58
+ callback,
32
59
containerId : containerIdFromProps ,
33
60
ignoreUnregistered,
34
- lateInvoke,
61
+ lateInvoke : lateInvokeFromProps ,
35
62
listenerTag,
36
63
statePath,
64
+ switchOff,
37
65
} : Args < TState > ) => {
38
66
const listenerIdRef = useRef < string > (
39
67
`${ listenerTag ? `${ listenerTag } :` : "" } ${ getUniqueId ( ) } `
@@ -47,22 +75,31 @@ export const useSelector = <TState extends Object, TAction extends Action>({
47
75
ignoreUnregistered :
48
76
typeof ignoreUnregistered === "boolean" ? ignoreUnregistered : true ,
49
77
} ) || { } ;
78
+ const lateInvoke =
79
+ typeof lateInvokeFromProps === "boolean" ? lateInvokeFromProps : true ;
50
80
const [ currentState , setCurrentState ] = useState ( initialState ) ;
51
81
useEffect ( ( ) => {
52
82
return ( ) => {
53
83
unmountedRef . current = true ;
54
84
} ;
55
85
} , [ ] ) ;
56
86
useEffect ( ( ) => {
87
+ if ( switchOff ) {
88
+ return ;
89
+ }
90
+ const statePaths =
91
+ typeof statePath === "string"
92
+ ? [ statePath ]
93
+ : ( statePath as TKnownStatePath < TState > [ ] ) ;
57
94
const statePathsLookup : Record <
58
95
TKnownStatePath < TState > ,
59
96
true
60
- > = statePath . reduce ( ( acc , path ) => {
97
+ > = statePaths . reduce ( ( acc , path ) => {
61
98
acc [ path ] = true ;
62
99
return acc ;
63
100
} , { } as Record < TKnownStatePath < TState > , true > ) ;
64
101
registerStateChangedCallback < TState , TAction > ( {
65
- callback : ( { changedPaths, newState } ) => {
102
+ callback : ( { action , changedPaths, newState, oldState } ) => {
66
103
/**
67
104
* Prevent receiving this React warning
68
105
* "Can't perform a React state update on an unmounted component.
@@ -79,15 +116,22 @@ export const useSelector = <TState extends Object, TAction extends Action>({
79
116
) {
80
117
setCurrentState ( newState ) ;
81
118
}
119
+ if ( callback ) {
120
+ callback ( { action, changedPaths, newState, oldState } ) ;
121
+ }
82
122
} ,
83
- lateInvoke : typeof lateInvoke === "boolean" ? lateInvoke : true ,
84
- listenerId : listenerIdRef . current ,
85
123
containerId,
124
+ lateInvoke,
125
+ listenerId : listenerIdRef . current ,
86
126
statePath : "*" ,
87
127
} ) ;
88
128
return ( ) => {
129
+ if ( switchOff ) {
130
+ return ;
131
+ }
89
132
unregisterStateChangedCallback < TState > ( {
90
133
containerId,
134
+ lateInvoke,
91
135
listenerId : listenerIdRef . current ,
92
136
statePath : "*" ,
93
137
} ) ;
0 commit comments