@@ -161,6 +161,7 @@ const INITIAL_STATE = {
161161
162162class Tests extends React . Component < DevToolProps , State > {
163163 state = INITIAL_STATE ;
164+ draftState : State | null = null ;
164165
165166 listener : ( ) => void ;
166167
@@ -176,13 +177,54 @@ class Tests extends React.Component<DevToolProps, State> {
176177 }
177178 }
178179
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+
179218 UNSAFE_componentWillReceiveProps ( nextProps : DevToolProps ) {
180219 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+ ) ;
186228 }
187229
188230 if ( this . props . hidden && ! nextProps . hidden ) {
@@ -191,19 +233,24 @@ class Tests extends React.Component<DevToolProps, State> {
191233 }
192234
193235 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+ ) ;
198243 } ;
199244
200245 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
207254 ) ;
208255 } ;
209256
@@ -231,7 +278,7 @@ class Tests extends React.Component<DevToolProps, State> {
231278 if ( this . props . updateStatus ) {
232279 this . props . updateStatus ( 'clear' ) ;
233280 }
234- this . setState ( INITIAL_STATE ) ;
281+ this . setStateDebounced ( INITIAL_STATE , 0 ) ;
235282 break ;
236283 }
237284 case 'test_count' : {
@@ -247,15 +294,21 @@ class Tests extends React.Component<DevToolProps, State> {
247294 if ( this . props . updateStatus ) {
248295 this . props . updateStatus ( 'clear' ) ;
249296 }
250- this . setState ( {
251- running : true ,
252- } ) ;
297+ this . setStateDebounced (
298+ {
299+ running : true ,
300+ } ,
301+ 0
302+ ) ;
253303 break ;
254304 }
255305 case messages . TOTAL_TEST_END : {
256- this . setState ( {
257- running : false ,
258- } ) ;
306+ this . setStateDebounced (
307+ {
308+ running : false ,
309+ } ,
310+ 0
311+ ) ;
259312
260313 const files = Object . keys ( this . state . files ) ;
261314 const failingTests = files . filter (
@@ -280,7 +333,7 @@ class Tests extends React.Component<DevToolProps, State> {
280333 }
281334
282335 case messages . ADD_FILE : {
283- this . setState ( oldState =>
336+ this . setStateDebounced ( oldState =>
284337 immer ( oldState , state => {
285338 state . files [ data . path ] = {
286339 tests : { } ,
@@ -293,7 +346,7 @@ class Tests extends React.Component<DevToolProps, State> {
293346 break ;
294347 }
295348 case 'remove_file' : {
296- this . setState ( oldState =>
349+ this . setStateDebounced ( oldState =>
297350 immer ( oldState , state => {
298351 if ( state . files [ data . path ] ) {
299352 delete state . files [ data . path ] ;
@@ -305,7 +358,7 @@ class Tests extends React.Component<DevToolProps, State> {
305358 break ;
306359 }
307360 case messages . FILE_ERROR : {
308- this . setState ( oldState =>
361+ this . setStateDebounced ( oldState =>
309362 immer ( oldState , state => {
310363 if ( state . files [ data . path ] ) {
311364 state . files [ data . path ] . fileError = data . error ;
@@ -325,7 +378,7 @@ class Tests extends React.Component<DevToolProps, State> {
325378 case messages . ADD_TEST : {
326379 const testName = [ ...this . currentDescribeBlocks , data . testName ] ;
327380
328- this . setState ( oldState =>
381+ this . setStateDebounced ( oldState =>
329382 immer ( oldState , state => {
330383 if ( ! state . files [ data . path ] ) {
331384 state . files [ data . path ] = {
@@ -351,7 +404,7 @@ class Tests extends React.Component<DevToolProps, State> {
351404 const { test } = data ;
352405 const testName = [ ...test . blocks , test . name ] ;
353406
354- this . setState ( oldState =>
407+ this . setStateDebounced ( oldState =>
355408 immer ( oldState , state => {
356409 if ( ! state . files [ test . path ] ) {
357410 state . files [ test . path ] = {
@@ -382,7 +435,7 @@ class Tests extends React.Component<DevToolProps, State> {
382435 const { test } = data ;
383436 const testName = [ ...test . blocks , test . name ] ;
384437
385- this . setState ( oldState =>
438+ this . setStateDebounced ( oldState =>
386439 immer ( oldState , state => {
387440 if ( ! state . files [ test . path ] ) {
388441 return ;
@@ -471,36 +524,34 @@ class Tests extends React.Component<DevToolProps, State> {
471524 } ;
472525
473526 toggleWatching = ( ) => {
527+ this . setStateDebounced ( state => ( { watching : ! state . watching } ) , 0 ) ;
474528 dispatch ( {
475529 type : 'set-test-watching' ,
476530 watching : ! this . state . watching ,
477531 } ) ;
478- this . setState ( state => ( { watching : ! state . watching } ) ) ;
479532 } ;
480533
481534 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' ,
486538 } ) ;
487539 } ;
488540
489541 runTests = ( file : File ) => {
490- this . setState (
542+ this . setStateDebounced (
491543 oldState =>
492544 immer ( oldState , state => {
493545 if ( state . files [ file . fileName ] ) {
494546 state . files [ file . fileName ] . tests = { } ;
495547 }
496548 } ) ,
497- ( ) => {
498- dispatch ( {
499- type : 'run-tests' ,
500- path : file . fileName ,
501- } ) ;
502- }
549+ 0
503550 ) ;
551+ dispatch ( {
552+ type : 'run-tests' ,
553+ path : file . fileName ,
554+ } ) ;
504555 } ;
505556
506557 openFile = ( path : string ) => {
0 commit comments