@@ -78,6 +78,7 @@ export type Capabilities = {|
7878export default class Store extends EventEmitter < { |
7979 collapseNodesByDefault : [ ] ,
8080 componentFilters : [ ] ,
81+ error : [ Error ] ,
8182 mutated : [ [ Array < number > , Map < number , number > ] ] ,
8283 recordChangeDescriptions : [ ] ,
8384 roots : [ ] ,
@@ -270,12 +271,14 @@ export default class Store extends EventEmitter<{|
270271 assertMapSizeMatchesRootCount ( map : Map < any , any > , mapName : string ) {
271272 const expectedSize = this . roots . length ;
272273 if ( map . size !== expectedSize ) {
273- throw new Error (
274- `Expected ${ mapName } to contain ${ expectedSize } items, but it contains ${
275- map . size
276- } items\n\n${ inspect ( map , {
277- depth : 20 ,
278- } ) } `,
274+ this . _throwAndEmitError (
275+ Error (
276+ `Expected ${ mapName } to contain ${ expectedSize } items, but it contains ${
277+ map . size
278+ } items\n\n${ inspect ( map , {
279+ depth : 20 ,
280+ } ) } `,
281+ ) ,
279282 ) ;
280283 }
281284 }
@@ -301,7 +304,9 @@ export default class Store extends EventEmitter<{|
301304 if ( this . _profilerStore . isProfiling ) {
302305 // Re-mounting a tree while profiling is in progress might break a lot of assumptions.
303306 // If necessary, we could support this- but it doesn't seem like a necessary use case.
304- throw Error ( 'Cannot modify filter preferences while profiling' ) ;
307+ this . _throwAndEmitError (
308+ Error ( 'Cannot modify filter preferences while profiling' ) ,
309+ ) ;
305310 }
306311
307312 // Filter updates are expensive to apply (since they impact the entire tree).
@@ -607,7 +612,7 @@ export default class Store extends EventEmitter<{|
607612 }
608613
609614 if ( depth === 0 ) {
610- throw Error ( 'Invalid owners list' ) ;
615+ this . _throwAndEmitError ( Error ( 'Invalid owners list' ) ) ;
611616 }
612617
613618 list . push ( { ...innerElement , depth} ) ;
@@ -667,7 +672,7 @@ export default class Store extends EventEmitter<{|
667672 if ( element !== null ) {
668673 if ( isCollapsed ) {
669674 if ( element . type === ElementTypeRoot ) {
670- throw Error ( 'Root nodes cannot be collapsed' ) ;
675+ this . _throwAndEmitError ( Error ( 'Root nodes cannot be collapsed' ) ) ;
671676 }
672677
673678 if ( ! element . isCollapsed ) {
@@ -825,8 +830,10 @@ export default class Store extends EventEmitter<{|
825830 i += 3 ;
826831
827832 if ( this . _idToElement . has ( id ) ) {
828- throw Error (
829- `Cannot add node "${ id } " because a node with that id is already in the Store.` ,
833+ this . _throwAndEmitError (
834+ Error (
835+ `Cannot add node "${ id } " because a node with that id is already in the Store.` ,
836+ ) ,
830837 ) ;
831838 }
832839
@@ -888,8 +895,10 @@ export default class Store extends EventEmitter<{|
888895 }
889896
890897 if ( ! this . _idToElement . has ( parentID ) ) {
891- throw Error (
892- `Cannot add child "${ id } " to parent "${ parentID } " because parent node was not found in the Store.` ,
898+ this . _throwAndEmitError (
899+ Error (
900+ `Cannot add child "${ id } " to parent "${ parentID } " because parent node was not found in the Store.` ,
901+ ) ,
893902 ) ;
894903 }
895904
@@ -940,8 +949,10 @@ export default class Store extends EventEmitter<{|
940949 const id = ( ( operations [ i ] : any ) : number ) ;
941950
942951 if ( ! this . _idToElement . has ( id ) ) {
943- throw Error (
944- `Cannot remove node "${ id } " because no matching node was found in the Store.` ,
952+ this . _throwAndEmitError (
953+ Error (
954+ `Cannot remove node "${ id } " because no matching node was found in the Store.` ,
955+ ) ,
945956 ) ;
946957 }
947958
@@ -950,7 +961,9 @@ export default class Store extends EventEmitter<{|
950961 const element = ( ( this . _idToElement . get ( id ) : any ) : Element ) ;
951962 const { children, ownerID, parentID, weight} = element ;
952963 if ( children . length > 0 ) {
953- throw new Error ( `Node "${ id } " was removed before its children.` ) ;
964+ this . _throwAndEmitError (
965+ Error ( `Node "${ id } " was removed before its children.` ) ,
966+ ) ;
954967 }
955968
956969 this . _idToElement . delete ( id ) ;
@@ -972,8 +985,10 @@ export default class Store extends EventEmitter<{|
972985 }
973986 parentElement = ( ( this . _idToElement . get ( parentID ) : any ) : Element ) ;
974987 if ( parentElement === undefined ) {
975- throw Error (
976- `Cannot remove node "${ id } " from parent "${ parentID } " because no matching node was found in the Store.` ,
988+ this . _throwAndEmitError (
989+ Error (
990+ `Cannot remove node "${ id } " from parent "${ parentID } " because no matching node was found in the Store.` ,
991+ ) ,
977992 ) ;
978993 }
979994 const index = parentElement . children . indexOf ( id ) ;
@@ -1033,16 +1048,20 @@ export default class Store extends EventEmitter<{|
10331048 i += 3 ;
10341049
10351050 if ( ! this . _idToElement . has ( id ) ) {
1036- throw Error (
1037- `Cannot reorder children for node "${ id } " because no matching node was found in the Store.` ,
1051+ this . _throwAndEmitError (
1052+ Error (
1053+ `Cannot reorder children for node "${ id } " because no matching node was found in the Store.` ,
1054+ ) ,
10381055 ) ;
10391056 }
10401057
10411058 const element = ( ( this . _idToElement . get ( id ) : any ) : Element ) ;
10421059 const children = element . children ;
10431060 if ( children . length !== numChildren ) {
1044- throw Error (
1045- `Children cannot be added or removed during a reorder operation.` ,
1061+ this . _throwAndEmitError (
1062+ Error (
1063+ `Children cannot be added or removed during a reorder operation.` ,
1064+ ) ,
10461065 ) ;
10471066 }
10481067
@@ -1087,7 +1106,9 @@ export default class Store extends EventEmitter<{|
10871106 haveErrorsOrWarningsChanged = true ;
10881107 break ;
10891108 default :
1090- throw Error ( `Unsupported Bridge operation "${ operation } "` ) ;
1109+ this . _throwAndEmitError (
1110+ Error ( `Unsupported Bridge operation "${ operation } "` ) ,
1111+ ) ;
10911112 }
10921113 }
10931114
@@ -1251,4 +1272,17 @@ export default class Store extends EventEmitter<{|
12511272
12521273 this . emit ( 'unsupportedBridgeProtocolDetected' ) ;
12531274 } ;
1275+
1276+ // The Store should never throw an Error without also emitting an event.
1277+ // Otherwise Store errors will be invisible to users,
1278+ // but the downstream errors they cause will be reported as bugs.
1279+ // For example, https://github.com/facebook/react/issues/21402
1280+ // Emitting an error event allows the ErrorBoundary to show the original error.
1281+ _throwAndEmitError ( error : Error ) {
1282+ this . emit ( 'error' , error ) ;
1283+
1284+ // Throwing is still valuable for local development
1285+ // and for unit testing the Store itself.
1286+ throw error ;
1287+ }
12541288}
0 commit comments