1212
1313'use strict' ;
1414
15+ import type { TrappedError } from 'ReactFiberErrorBoundary' ;
1516import type { Fiber } from 'ReactFiber' ;
1617import type { FiberRoot } from 'ReactFiberRoot' ;
1718import type { HostConfig } from 'ReactFiberReconciler' ;
2324 HostComponent,
2425 HostText,
2526} = ReactTypeOfWork ;
27+ var { trapError } = require ( 'ReactFiberErrorBoundary' ) ;
2628var { callCallbacks } = require ( 'ReactFiberUpdateQueue' ) ;
2729
2830var {
@@ -155,72 +157,94 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
155157 }
156158 }
157159
158- function commitNestedUnmounts ( root : Fiber ) {
160+ function commitNestedUnmounts ( root : Fiber ) : Array < TrappedError > | null {
161+ // Since errors are rare, we allocate this array on demand.
162+ let trappedErrors = null ;
163+
159164 // While we're inside a removed host node we don't want to call
160165 // removeChild on the inner nodes because they're removed by the top
161166 // call anyway. We also want to call componentWillUnmount on all
162167 // composites before this host node is removed from the tree. Therefore
163168 // we do an inner loop while we're still inside the host node.
164169 let node : Fiber = root ;
165170 while ( true ) {
166- commitUnmount ( node ) ;
171+ const error = commitUnmount ( node ) ;
172+ if ( error ) {
173+ trappedErrors = trappedErrors || [ ] ;
174+ trappedErrors . push ( error ) ;
175+ }
167176 if ( node . child ) {
168177 // TODO: Coroutines need to visit the stateNode.
169178 node = node . child ;
170179 continue ;
171180 }
172181 if ( node === root ) {
173- return ;
182+ return trappedErrors ;
174183 }
175184 while ( ! node . sibling ) {
176185 if ( ! node . return || node . return === root ) {
177- return ;
186+ return trappedErrors ;
178187 }
179188 node = node . return ;
180189 }
181190 node = node . sibling ;
182191 }
192+ return trappedErrors ;
183193 }
184194
185- function unmountHostComponents ( parent , current ) {
195+ function unmountHostComponents ( parent , current ) : Array < TrappedError > | null {
196+ // Since errors are rare, we allocate this array on demand.
197+ let trappedErrors = null ;
198+
186199 // We only have the top Fiber that was inserted but we need recurse down its
187200 // children to find all the terminal nodes.
188201 let node : Fiber = current ;
189202 while ( true ) {
190203 if ( node . tag === HostComponent || node . tag === HostText ) {
191- commitNestedUnmounts ( node ) ;
204+ const errors = commitNestedUnmounts ( node ) ;
205+ if ( errors ) {
206+ if ( ! trappedErrors ) {
207+ trappedErrors = errors ;
208+ } else {
209+ trappedErrors . push . apply ( trappedErrors , errors ) ;
210+ }
211+ }
192212 // After all the children have unmounted, it is now safe to remove the
193213 // node from the tree.
194214 if ( parent ) {
195215 removeChild ( parent , node . stateNode ) ;
196216 }
197217 } else {
198- commitUnmount ( node ) ;
218+ const error = commitUnmount ( node ) ;
219+ if ( error ) {
220+ trappedErrors = trappedErrors || [ ] ;
221+ trappedErrors . push ( error ) ;
222+ }
199223 if ( node . child ) {
200224 // TODO: Coroutines need to visit the stateNode.
201225 node = node . child ;
202226 continue ;
203227 }
204228 }
205229 if ( node === current ) {
206- return ;
230+ return trappedErrors ;
207231 }
208232 while ( ! node . sibling ) {
209233 if ( ! node . return || node . return === current ) {
210- return ;
234+ return trappedErrors ;
211235 }
212236 node = node . return ;
213237 }
214238 node = node . sibling ;
215239 }
240+ return trappedErrors ;
216241 }
217242
218- function commitDeletion ( current : Fiber ) : void {
243+ function commitDeletion ( current : Fiber ) : Array < TrappedError > | null {
219244 // Recursively delete all host nodes from the parent.
220- // TODO: Error handling.
221245 const parent = getHostParent ( current ) ;
222-
223- unmountHostComponents ( parent , current ) ;
246+ // Detach refs and call componentWillUnmount() on the whole subtree.
247+ const trappedErrors = unmountHostComponents ( parent , current ) ;
224248
225249 // Cut off the return pointers to disconnect it from the tree. Ideally, we
226250 // should clear the child pointer of the parent alternate to let this
@@ -233,21 +257,29 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
233257 current . alternate . child = null ;
234258 current . alternate . return = null ;
235259 }
260+
261+ return trappedErrors ;
236262 }
237263
238- function commitUnmount ( current : Fiber ) : void {
264+ function commitUnmount ( current : Fiber ) : TrappedError | null {
239265 switch ( current . tag ) {
240266 case ClassComponent : {
241267 detachRef ( current ) ;
242268 const instance = current . stateNode ;
243269 if ( typeof instance . componentWillUnmount === 'function' ) {
244- instance . componentWillUnmount ( ) ;
270+ const error = tryCallComponentWillUnmount ( instance ) ;
271+ if ( error ) {
272+ return trapError ( current , error ) ;
273+ }
245274 }
246- return ;
275+ return null ;
247276 }
248277 case HostComponent : {
249278 detachRef ( current ) ;
250- return ;
279+ return null ;
280+ }
281+ default : {
282+ return null ;
251283 }
252284 }
253285 }
@@ -292,19 +324,20 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
292324 }
293325 }
294326
295- function commitLifeCycles ( current : ?Fiber , finishedWork : Fiber ) : void {
327+ function commitLifeCycles ( current : ?Fiber , finishedWork : Fiber ) : TrappedError | null {
296328 switch ( finishedWork . tag ) {
297329 case ClassComponent : {
298330 const instance = finishedWork . stateNode ;
331+ let error = null ;
299332 if ( ! current ) {
300333 if ( typeof instance . componentDidMount === 'function' ) {
301- instance . componentDidMount ( ) ;
334+ error = tryCallComponentDidMount ( instance ) ;
302335 }
303336 } else {
304337 if ( typeof instance . componentDidUpdate === 'function' ) {
305338 const prevProps = current . memoizedProps ;
306339 const prevState = current . memoizedState ;
307- instance . componentDidUpdate ( prevProps , prevState ) ;
340+ error = tryCallComponentDidUpdate ( instance , prevProps , prevState ) ;
308341 }
309342 }
310343 // Clear updates from current fiber. This must go before the callbacks
@@ -320,7 +353,10 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
320353 callCallbacks ( callbackList , instance ) ;
321354 }
322355 attachRef ( current , finishedWork , instance ) ;
323- return ;
356+ if ( error ) {
357+ return trapError ( finishedWork , error ) ;
358+ }
359+ return null ;
324360 }
325361 case HostContainer : {
326362 const instance = finishedWork . stateNode ;
@@ -333,17 +369,44 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
333369 case HostComponent : {
334370 const instance : I = finishedWork . stateNode ;
335371 attachRef ( current , finishedWork , instance ) ;
336- return ;
372+ return null ;
337373 }
338374 case HostText : {
339375 // We have no life-cycles associated with text.
340- return ;
376+ return null ;
341377 }
342378 default :
343379 throw new Error ( 'This unit of work tag should not have side-effects.' ) ;
344380 }
345381 }
346382
383+ function tryCallComponentDidMount ( instance ) {
384+ try {
385+ instance . componentDidMount ( ) ;
386+ return null ;
387+ } catch ( error ) {
388+ return error ;
389+ }
390+ }
391+
392+ function tryCallComponentDidUpdate ( instance , prevProps , prevState ) {
393+ try {
394+ instance . componentDidUpdate ( prevProps , prevState ) ;
395+ return null ;
396+ } catch ( error ) {
397+ return error ;
398+ }
399+ }
400+
401+ function tryCallComponentWillUnmount ( instance ) {
402+ try {
403+ instance . componentWillUnmount ( ) ;
404+ return null ;
405+ } catch ( error ) {
406+ return error ;
407+ }
408+ }
409+
347410 return {
348411 commitInsertion,
349412 commitDeletion,
0 commit comments