@@ -74,31 +74,37 @@ describe('useMemoCache()', () => {
74
74
let setX ;
75
75
let forceUpdate ;
76
76
function Component ( props ) {
77
- const cache = useMemoCache ( 4 ) ;
77
+ const cache = useMemoCache ( 5 ) ;
78
78
79
79
// x is used to produce a `data` object passed to the child
80
80
const [ x , _setX ] = useState ( 0 ) ;
81
81
setX = _setX ;
82
- const c_x = x !== cache [ 0 ] ;
83
- cache [ 0 ] = x ;
84
82
85
83
// n is passed as-is to the child as a cache breaker
86
84
const [ n , setN ] = useState ( 0 ) ;
87
85
forceUpdate = ( ) => setN ( a => a + 1 ) ;
88
- const c_n = n !== cache [ 1 ] ;
89
- cache [ 1 ] = n ;
90
86
87
+ const c_0 = x !== cache [ 0 ] ;
91
88
let data ;
92
- if ( c_x ) {
93
- data = cache [ 2 ] = { text : `Count ${ x } ` } ;
89
+ if ( c_0 ) {
90
+ data = { text : `Count ${ x } ` } ;
91
+ cache [ 0 ] = x ;
92
+ cache [ 1 ] = data ;
94
93
} else {
95
- data = cache [ 2 ] ;
94
+ data = cache [ 1 ] ;
96
95
}
97
- if ( c_x || c_n ) {
98
- return ( cache [ 3 ] = < Text data = { data } n = { n } /> ) ;
96
+ const c_2 = x !== cache [ 2 ] ;
97
+ const c_3 = n !== cache [ 3 ] ;
98
+ let t0 ;
99
+ if ( c_2 || c_3 ) {
100
+ t0 = < Text data = { data } n = { n } /> ;
101
+ cache [ 2 ] = x ;
102
+ cache [ 3 ] = n ;
103
+ cache [ 4 ] = t0 ;
99
104
} else {
100
- return cache [ 3 ] ;
105
+ t0 = cache [ 4 ] ;
101
106
}
107
+ return t0 ;
102
108
}
103
109
let data ;
104
110
const Text = jest . fn ( function Text ( props ) {
@@ -135,132 +141,117 @@ describe('useMemoCache()', () => {
135
141
136
142
// @gate enableUseMemoCacheHook
137
143
test ( 'update component using cache with setstate during render' , async ( ) => {
138
- let setX ;
139
144
let setN ;
140
145
function Component ( props ) {
141
- const cache = useMemoCache ( 4 ) ;
146
+ const cache = useMemoCache ( 5 ) ;
142
147
143
148
// x is used to produce a `data` object passed to the child
144
- const [ x , _setX ] = useState ( 0 ) ;
145
- setX = _setX ;
146
- const c_x = x !== cache [ 0 ] ;
147
- cache [ 0 ] = x ;
149
+ const [ x ] = useState ( 0 ) ;
150
+
151
+ const c_0 = x !== cache [ 0 ] ;
152
+ let data ;
153
+ if ( c_0 ) {
154
+ data = { text : `Count ${ x } ` } ;
155
+ cache [ 0 ] = x ;
156
+ cache [ 1 ] = data ;
157
+ } else {
158
+ data = cache [ 1 ] ;
159
+ }
148
160
149
161
// n is passed as-is to the child as a cache breaker
150
162
const [ n , _setN ] = useState ( 0 ) ;
151
163
setN = _setN ;
152
- const c_n = n !== cache [ 1 ] ;
153
- cache [ 1 ] = n ;
154
164
155
- // NOTE: setstate and early return here means that x will update
156
- // without the data value being updated. Subsequent renders could
157
- // therefore think that c_x = false (hasn't changed) and skip updating
158
- // data.
159
- // The memoizing compiler will have to handle this case, but the runtime
160
- // can help by falling back to resetting the cache if a setstate occurs
161
- // during render (this mirrors what we do for useMemo and friends)
162
165
if ( n === 1 ) {
163
166
setN ( 2 ) ;
164
167
return ;
165
168
}
166
169
167
- let data ;
168
- if ( c_x ) {
169
- data = cache [ 2 ] = { text : `Count ${ x } ` } ;
170
+ const c_2 = x !== cache [ 2 ] ;
171
+ const c_3 = n !== cache [ 3 ] ;
172
+ let t0 ;
173
+ if ( c_2 || c_3 ) {
174
+ t0 = < Text data = { data } n = { n } /> ;
175
+ cache [ 2 ] = x ;
176
+ cache [ 3 ] = n ;
177
+ cache [ 4 ] = t0 ;
170
178
} else {
171
- data = cache [ 2 ] ;
172
- }
173
- if ( c_x || c_n ) {
174
- return ( cache [ 3 ] = < Text data = { data } n = { n } /> ) ;
175
- } else {
176
- return cache [ 3 ] ;
179
+ t0 = cache [ 4 ] ;
177
180
}
181
+ return t0 ;
178
182
}
179
183
let data ;
180
184
const Text = jest . fn ( function Text ( props ) {
181
185
data = props . data ;
182
- return data . text ;
186
+ return ` ${ data . text } (n= ${ props . n } )` ;
183
187
} ) ;
184
188
185
189
const root = ReactNoop . createRoot ( ) ;
186
190
await act ( ( ) => {
187
191
root . render ( < Component /> ) ;
188
192
} ) ;
189
- expect ( root ) . toMatchRenderedOutput ( 'Count 0' ) ;
193
+ expect ( root ) . toMatchRenderedOutput ( 'Count 0 (n=0) ' ) ;
190
194
expect ( Text ) . toBeCalledTimes ( 1 ) ;
191
195
const data0 = data ;
192
196
193
- // Simultaneously trigger an update to x (should create a new data value)
194
- // and trigger the setState+early return. The runtime should reset the cache
195
- // to avoid an inconsistency
197
+ // Trigger an update that will cause a setState during render. The `data` prop
198
+ // does not depend on `n`, and should remain cached.
196
199
await act ( ( ) => {
197
- setX ( 1 ) ;
198
200
setN ( 1 ) ;
199
201
} ) ;
200
- expect ( root ) . toMatchRenderedOutput ( 'Count 1 ' ) ;
202
+ expect ( root ) . toMatchRenderedOutput ( 'Count 0 (n=2) ' ) ;
201
203
expect ( Text ) . toBeCalledTimes ( 2 ) ;
202
- expect ( data ) . not . toBe ( data0 ) ;
203
- const data1 = data ;
204
-
205
- // Forcing an unrelated update shouldn't recreate the
206
- // data object.
207
- await act ( ( ) => {
208
- setN ( 3 ) ;
209
- } ) ;
210
- expect ( root ) . toMatchRenderedOutput ( 'Count 1' ) ;
211
- expect ( Text ) . toBeCalledTimes ( 3 ) ;
212
- expect ( data ) . toBe ( data1 ) ; // confirm that the cache persisted across renders
204
+ expect ( data ) . toBe ( data0 ) ;
213
205
} ) ;
214
206
215
207
// @gate enableUseMemoCacheHook
216
208
test ( 'update component using cache with throw during render' , async ( ) => {
217
- let setX ;
218
209
let setN ;
219
210
let shouldFail = true ;
220
211
function Component ( props ) {
221
- const cache = useMemoCache ( 4 ) ;
212
+ const cache = useMemoCache ( 5 ) ;
222
213
223
214
// x is used to produce a `data` object passed to the child
224
- const [ x , _setX ] = useState ( 0 ) ;
225
- setX = _setX ;
226
- const c_x = x !== cache [ 0 ] ;
227
- cache [ 0 ] = x ;
215
+ const [ x ] = useState ( 0 ) ;
216
+
217
+ const c_0 = x !== cache [ 0 ] ;
218
+ let data ;
219
+ if ( c_0 ) {
220
+ data = { text : `Count ${ x } ` } ;
221
+ cache [ 0 ] = x ;
222
+ cache [ 1 ] = data ;
223
+ } else {
224
+ data = cache [ 1 ] ;
225
+ }
228
226
229
227
// n is passed as-is to the child as a cache breaker
230
228
const [ n , _setN ] = useState ( 0 ) ;
231
229
setN = _setN ;
232
- const c_n = n !== cache [ 1 ] ;
233
- cache [ 1 ] = n ;
234
230
235
- // NOTE the initial failure will trigger a re-render, after which the function
236
- // will early return. This validates that the runtime resets the cache on error:
237
- // if it doesn't the cache will be corrupt, with the cached version of data
238
- // out of data from the cached version of x.
239
231
if ( n === 1 ) {
240
232
if ( shouldFail ) {
241
233
shouldFail = false ;
242
234
throw new Error ( 'failed' ) ;
243
235
}
244
- setN ( 2 ) ;
245
- return ;
246
236
}
247
237
248
- let data ;
249
- if ( c_x ) {
250
- data = cache [ 2 ] = { text : `Count ${ x } ` } ;
238
+ const c_2 = x !== cache [ 2 ] ;
239
+ const c_3 = n !== cache [ 3 ] ;
240
+ let t0 ;
241
+ if ( c_2 || c_3 ) {
242
+ t0 = < Text data = { data } n = { n } /> ;
243
+ cache [ 2 ] = x ;
244
+ cache [ 3 ] = n ;
245
+ cache [ 4 ] = t0 ;
251
246
} else {
252
- data = cache [ 2 ] ;
253
- }
254
- if ( c_x || c_n ) {
255
- return ( cache [ 3 ] = < Text data = { data } n = { n } /> ) ;
256
- } else {
257
- return cache [ 3 ] ;
247
+ t0 = cache [ 4 ] ;
258
248
}
249
+ return t0 ;
259
250
}
260
251
let data ;
261
252
const Text = jest . fn ( function Text ( props ) {
262
253
data = props . data ;
263
- return data . text ;
254
+ return ` ${ data . text } (n= ${ props . n } )` ;
264
255
} ) ;
265
256
266
257
spyOnDev ( console , 'error' ) ;
@@ -273,30 +264,25 @@ describe('useMemoCache()', () => {
273
264
</ ErrorBoundary > ,
274
265
) ;
275
266
} ) ;
276
- expect ( root ) . toMatchRenderedOutput ( 'Count 0' ) ;
267
+ expect ( root ) . toMatchRenderedOutput ( 'Count 0 (n=0) ' ) ;
277
268
expect ( Text ) . toBeCalledTimes ( 1 ) ;
278
269
const data0 = data ;
279
270
280
- // Simultaneously trigger an update to x (should create a new data value)
281
- // and trigger the setState+early return. The runtime should reset the cache
282
- // to avoid an inconsistency
283
271
await act ( ( ) => {
284
- // this update bumps the count
285
- setX ( 1 ) ;
286
272
// this triggers a throw.
287
273
setN ( 1 ) ;
288
274
} ) ;
289
- expect ( root ) . toMatchRenderedOutput ( 'Count 1 ' ) ;
275
+ expect ( root ) . toMatchRenderedOutput ( 'Count 0 (n=1) ' ) ;
290
276
expect ( Text ) . toBeCalledTimes ( 2 ) ;
291
- expect ( data ) . not . toBe ( data0 ) ;
277
+ expect ( data ) . toBe ( data0 ) ;
292
278
const data1 = data ;
293
279
294
280
// Forcing an unrelated update shouldn't recreate the
295
281
// data object.
296
282
await act ( ( ) => {
297
- setN ( 3 ) ;
283
+ setN ( 2 ) ;
298
284
} ) ;
299
- expect ( root ) . toMatchRenderedOutput ( 'Count 1 ' ) ;
285
+ expect ( root ) . toMatchRenderedOutput ( 'Count 0 (n=2) ' ) ;
300
286
expect ( Text ) . toBeCalledTimes ( 3 ) ;
301
287
expect ( data ) . toBe ( data1 ) ; // confirm that the cache persisted across renders
302
288
} ) ;
0 commit comments