@@ -161,6 +161,7 @@ const INITIAL_STATE = {
161
161
162
162
class Tests extends React . Component < DevToolProps , State > {
163
163
state = INITIAL_STATE ;
164
+ draftState : State | null = null ;
164
165
165
166
listener : ( ) => void ;
166
167
@@ -176,13 +177,54 @@ class Tests extends React.Component<DevToolProps, State> {
176
177
}
177
178
}
178
179
180
+ setStateTimer = null ;
181
+ /**
182
+ * We can call setState 100s of times per second, which puts great strain
183
+ * on rendering from React. We debounce the rendering so that we flush changes
184
+ * after a while. This prevents the editor from getting stuck.
185
+ *
186
+ * Every setState call will have to go through this, otherwise we get race conditions
187
+ * where the underlying state has changed, but the draftState didn't change.
188
+ */
189
+ setStateDebounced = ( setStateFunc , time = 200 ) => {
190
+ const draftState = this . draftState || this . state ;
191
+
192
+ const newState =
193
+ typeof setStateFunc === 'function'
194
+ ? setStateFunc ( draftState , this . props )
195
+ : setStateFunc ;
196
+ this . draftState = { ...draftState , ...newState } ;
197
+
198
+ if ( this . setStateTimer ) {
199
+ clearTimeout ( this . setStateTimer ) ;
200
+ }
201
+
202
+ const updateFunc = ( ) => {
203
+ if ( this . draftState ) {
204
+ this . setState ( this . draftState ) ;
205
+ }
206
+
207
+ this . draftState = null ;
208
+ this . setStateTimer = null ;
209
+ } ;
210
+
211
+ if ( time === 0 ) {
212
+ updateFunc ( ) ;
213
+ } else {
214
+ this . setStateTimer = window . setTimeout ( updateFunc , time ) ;
215
+ }
216
+ } ;
217
+
179
218
UNSAFE_componentWillReceiveProps ( nextProps : DevToolProps ) {
180
219
if ( nextProps . sandboxId !== this . props . sandboxId ) {
181
- this . setState ( {
182
- files : { } ,
183
- selectedFilePath : null ,
184
- running : true ,
185
- } ) ;
220
+ this . setStateDebounced (
221
+ {
222
+ files : { } ,
223
+ selectedFilePath : null ,
224
+ running : true ,
225
+ } ,
226
+ 0
227
+ ) ;
186
228
}
187
229
188
230
if ( this . props . hidden && ! nextProps . hidden ) {
@@ -191,19 +233,24 @@ class Tests extends React.Component<DevToolProps, State> {
191
233
}
192
234
193
235
selectFile = ( file : File ) => {
194
- this . setState ( state => ( {
195
- selectedFilePath :
196
- file . fileName === state . selectedFilePath ? null : file . fileName ,
197
- } ) ) ;
236
+ this . setStateDebounced (
237
+ state => ( {
238
+ selectedFilePath :
239
+ file . fileName === state . selectedFilePath ? null : file . fileName ,
240
+ } ) ,
241
+ 0
242
+ ) ;
198
243
} ;
199
244
200
245
toggleFileExpansion = ( file : File ) => {
201
- this . setState ( oldState =>
202
- immer ( oldState , state => {
203
- state . fileExpansionState [ file . fileName ] = ! state . fileExpansionState [
204
- file . fileName
205
- ] ;
206
- } )
246
+ this . setStateDebounced (
247
+ oldState =>
248
+ immer ( oldState , state => {
249
+ state . fileExpansionState [ file . fileName ] = ! state . fileExpansionState [
250
+ file . fileName
251
+ ] ;
252
+ } ) ,
253
+ 0
207
254
) ;
208
255
} ;
209
256
@@ -231,7 +278,7 @@ class Tests extends React.Component<DevToolProps, State> {
231
278
if ( this . props . updateStatus ) {
232
279
this . props . updateStatus ( 'clear' ) ;
233
280
}
234
- this . setState ( INITIAL_STATE ) ;
281
+ this . setStateDebounced ( INITIAL_STATE , 0 ) ;
235
282
break ;
236
283
}
237
284
case 'test_count' : {
@@ -247,15 +294,21 @@ class Tests extends React.Component<DevToolProps, State> {
247
294
if ( this . props . updateStatus ) {
248
295
this . props . updateStatus ( 'clear' ) ;
249
296
}
250
- this . setState ( {
251
- running : true ,
252
- } ) ;
297
+ this . setStateDebounced (
298
+ {
299
+ running : true ,
300
+ } ,
301
+ 0
302
+ ) ;
253
303
break ;
254
304
}
255
305
case messages . TOTAL_TEST_END : {
256
- this . setState ( {
257
- running : false ,
258
- } ) ;
306
+ this . setStateDebounced (
307
+ {
308
+ running : false ,
309
+ } ,
310
+ 0
311
+ ) ;
259
312
260
313
const files = Object . keys ( this . state . files ) ;
261
314
const failingTests = files . filter (
@@ -280,7 +333,7 @@ class Tests extends React.Component<DevToolProps, State> {
280
333
}
281
334
282
335
case messages . ADD_FILE : {
283
- this . setState ( oldState =>
336
+ this . setStateDebounced ( oldState =>
284
337
immer ( oldState , state => {
285
338
state . files [ data . path ] = {
286
339
tests : { } ,
@@ -293,7 +346,7 @@ class Tests extends React.Component<DevToolProps, State> {
293
346
break ;
294
347
}
295
348
case 'remove_file' : {
296
- this . setState ( oldState =>
349
+ this . setStateDebounced ( oldState =>
297
350
immer ( oldState , state => {
298
351
if ( state . files [ data . path ] ) {
299
352
delete state . files [ data . path ] ;
@@ -305,7 +358,7 @@ class Tests extends React.Component<DevToolProps, State> {
305
358
break ;
306
359
}
307
360
case messages . FILE_ERROR : {
308
- this . setState ( oldState =>
361
+ this . setStateDebounced ( oldState =>
309
362
immer ( oldState , state => {
310
363
if ( state . files [ data . path ] ) {
311
364
state . files [ data . path ] . fileError = data . error ;
@@ -325,7 +378,7 @@ class Tests extends React.Component<DevToolProps, State> {
325
378
case messages . ADD_TEST : {
326
379
const testName = [ ...this . currentDescribeBlocks , data . testName ] ;
327
380
328
- this . setState ( oldState =>
381
+ this . setStateDebounced ( oldState =>
329
382
immer ( oldState , state => {
330
383
if ( ! state . files [ data . path ] ) {
331
384
state . files [ data . path ] = {
@@ -351,7 +404,7 @@ class Tests extends React.Component<DevToolProps, State> {
351
404
const { test } = data ;
352
405
const testName = [ ...test . blocks , test . name ] ;
353
406
354
- this . setState ( oldState =>
407
+ this . setStateDebounced ( oldState =>
355
408
immer ( oldState , state => {
356
409
if ( ! state . files [ test . path ] ) {
357
410
state . files [ test . path ] = {
@@ -382,7 +435,7 @@ class Tests extends React.Component<DevToolProps, State> {
382
435
const { test } = data ;
383
436
const testName = [ ...test . blocks , test . name ] ;
384
437
385
- this . setState ( oldState =>
438
+ this . setStateDebounced ( oldState =>
386
439
immer ( oldState , state => {
387
440
if ( ! state . files [ test . path ] ) {
388
441
return ;
@@ -471,36 +524,34 @@ class Tests extends React.Component<DevToolProps, State> {
471
524
} ;
472
525
473
526
toggleWatching = ( ) => {
527
+ this . setStateDebounced ( state => ( { watching : ! state . watching } ) , 0 ) ;
474
528
dispatch ( {
475
529
type : 'set-test-watching' ,
476
530
watching : ! this . state . watching ,
477
531
} ) ;
478
- this . setState ( state => ( { watching : ! state . watching } ) ) ;
479
532
} ;
480
533
481
534
runAllTests = ( ) => {
482
- this . setState ( { files : { } } , ( ) => {
483
- dispatch ( {
484
- type : 'run-all-tests' ,
485
- } ) ;
535
+ this . setStateDebounced ( { files : { } } , 0 ) ;
536
+ dispatch ( {
537
+ type : 'run-all-tests' ,
486
538
} ) ;
487
539
} ;
488
540
489
541
runTests = ( file : File ) => {
490
- this . setState (
542
+ this . setStateDebounced (
491
543
oldState =>
492
544
immer ( oldState , state => {
493
545
if ( state . files [ file . fileName ] ) {
494
546
state . files [ file . fileName ] . tests = { } ;
495
547
}
496
548
} ) ,
497
- ( ) => {
498
- dispatch ( {
499
- type : 'run-tests' ,
500
- path : file . fileName ,
501
- } ) ;
502
- }
549
+ 0
503
550
) ;
551
+ dispatch ( {
552
+ type : 'run-tests' ,
553
+ path : file . fileName ,
554
+ } ) ;
504
555
} ;
505
556
506
557
openFile = ( path : string ) => {
0 commit comments