3
3
* @author junbao <[email protected] >
4
4
*/
5
5
6
- import { Action , box , SelectableAction } from 'amos' ;
6
+ import { action , Action , type Box , box , isAmosObject , SelectableAction , selector } from 'amos' ;
7
7
import { resolveCacheKey } from 'amos-core' ;
8
8
import {
9
9
clone ,
@@ -14,36 +14,36 @@ import {
14
14
type WellPartial ,
15
15
} from 'amos-utils' ;
16
16
import { useEffect } from 'react' ;
17
+ import { useDispatch } from './context' ;
17
18
import { useSelector } from './useSelector' ;
18
19
19
- export interface QueryResultJSON < R > extends Pick < QueryResult < R > , 'status' | 'value' | 'error' > { }
20
+ export interface QueryResultJSON < R > extends Pick < QueryResult < R > , 'status' | 'value' > { }
20
21
21
22
export class QueryResult < R > implements JSONSerializable < QueryResultJSON < R > > {
22
23
/** @internal */
23
24
id : string | undefined ;
24
- status : 'pending' | 'fulfilled' | 'rejected' = 'pending' ;
25
+ /** @internal */
26
+ _nextId : string | undefined ;
27
+ status : 'initial' | 'pending' | 'fulfilled' | 'rejected' = 'initial' ;
25
28
promise : Defer < void > = defer ( ) ;
26
- value : Awaited < R > | undefined ;
27
- error : any ;
29
+ value : Awaited < R > | undefined = void 0 ;
30
+ error : any = void 0 ;
28
31
29
32
toJSON ( ) : QueryResultJSON < R > {
30
33
return {
31
34
status : this . status ,
32
35
value : this . value ,
33
- error : this . error ,
34
36
} ;
35
37
}
36
38
37
39
fromJS ( state : JSONState < QueryResultJSON < R > > ) : this {
38
- return clone ( this , {
40
+ const p = defer < void > ( ) ;
41
+ const r = clone ( this , {
39
42
...state ,
40
- promise :
41
- state . status === 'pending'
42
- ? defer ( )
43
- : state . status === 'rejected'
44
- ? Promise . reject ( state . error )
45
- : Promise . resolve ( state . value ) ,
43
+ promise : p ,
46
44
} as WellPartial < this> ) ;
45
+ r . isRejected ( ) ? p . reject ( new Error ( 'Server error' ) ) : r . isFulfilled ( ) ? p . resolve ( ) : void 0 ;
46
+ return r ;
47
47
}
48
48
49
49
isPending ( ) {
@@ -80,8 +80,12 @@ export class QueryResultMap
80
80
getItem ( key : string , id : string ) : QueryResult < any > {
81
81
let item = this . get ( key ) ;
82
82
if ( item ) {
83
- if ( ! item . id ) {
84
- item . id = id ;
83
+ if ( item . id === void 0 ) {
84
+ if ( item . _nextId === void 0 ) {
85
+ item . _nextId = id ;
86
+ } else if ( item . _nextId !== id ) {
87
+ item = void 0 ;
88
+ }
85
89
} else if ( item . id !== id ) {
86
90
item = void 0 ;
87
91
}
@@ -95,8 +99,21 @@ export class QueryResultMap
95
99
}
96
100
}
97
101
102
+ // Immutable state conflicts with react suspense use.
98
103
export const queryMapBox = box ( 'amos.queries' , ( ) => new QueryResultMap ( ) ) ;
99
104
105
+ export const selectQuery = selector ( ( select , key : string , id : string ) => {
106
+ return select ( queryMapBox ) . getItem ( key , id ) ;
107
+ } ) ;
108
+
109
+ export const updateQuery = action ( ( dispatch , select , key : string , query : QueryResult < any > ) => {
110
+ const map = select ( queryMapBox ) ;
111
+ if ( map . get ( key ) === query ) {
112
+ map . set ( key , clone ( query , { } ) ) ;
113
+ }
114
+ dispatch ( queryMapBox . setState ( map ) ) ;
115
+ } ) ;
116
+
100
117
export interface UseQuery {
101
118
< A extends any [ ] = any , R = any , S = any > (
102
119
action : SelectableAction < A , R , S > ,
@@ -115,11 +132,38 @@ export interface UseQuery {
115
132
*/
116
133
export const useQuery : UseQuery = ( action : Action | SelectableAction ) : [ any , QueryResult < any > ] => {
117
134
const select = useSelector ( ) ;
135
+ const dispatch = useDispatch ( ) ;
118
136
const key = resolveCacheKey ( select , action , action . conflictKey ) ;
119
- const result = select ( queryMapBox ) . getItem ( key , action . id ) ;
120
- useEffect ( ( ) => { } , [ key ] ) ;
137
+ const result = select ( selectQuery ( key , action . id ) ) ;
138
+ const shouldDispatch = result . status !== 'pending' && result . id !== void 0 ;
139
+ if ( shouldDispatch ) {
140
+ result . status = 'pending' ;
141
+ }
142
+ useEffect ( ( ) => {
143
+ if ( shouldDispatch ) {
144
+ ( async ( ) => {
145
+ try {
146
+ result . value = await dispatch ( action ) ;
147
+ result . status = 'fulfilled' ;
148
+ result . promise . resolve ( ) ;
149
+ } catch ( e ) {
150
+ result . error = e ;
151
+ result . status = 'rejected' ;
152
+ result . promise . reject ( e ) ;
153
+ }
154
+ dispatch ( updateQuery ( key , result ) ) ;
155
+ } ) ( ) ;
156
+ }
157
+ } , [ key , shouldDispatch ] ) ;
121
158
if ( 'selector' in action ) {
122
- return [ select ( action . selector ( ...action . args ) ) , result ] ;
159
+ return [
160
+ select (
161
+ isAmosObject < Box > ( action . selector , 'box' )
162
+ ? action . selector
163
+ : action . selector ( ...action . args ) ,
164
+ ) ,
165
+ result ,
166
+ ] ;
123
167
} else {
124
168
return [ result . value , result ] ;
125
169
}
0 commit comments