17
Container,
18
ChildSet,
19
} from './ReactFiberHostConfig';
20
+import type {SuspenseState} from './ReactFiberSuspenseComponent';
21
22
import {
23
IndeterminateComponent,
47
import {
48
createInstance,
49
createTextInstance,
50
+ createHiddenTextInstance,
51
appendInitialChild,
52
finalizeInitialChildren,
53
prepareUpdate,
54
supportsMutation,
55
supportsPersistence,
56
cloneInstance,
57
+ cloneHiddenInstance,
58
+ cloneUnhiddenInstance,
59
createContainerChildSet,
60
appendChildToContainerChildSet,
61
finalizeContainerChildren,
88
workInProgress.effectTag |= Ref;
89
}
90
87
-function appendAllChildren(parent: Instance, workInProgress: Fiber) {
88
- // We only have the top Fiber that was created but we need recurse down its
89
- // children to find all the terminal nodes.
90
- let node = workInProgress.child;
91
- while (node !== null) {
92
- if (node.tag === HostComponent || node.tag === HostText) {
93
- appendInitialChild(parent, node.stateNode);
94
- } else if (node.tag === HostPortal) {
95
- // If we have a portal child, then we don't want to traverse
96
- // down its children. Instead, we'll get insertions from each child in
97
- // the portal directly.
98
- } else if (node.child !== null) {
99
- node.child.return = node;
100
- node = node.child;
101
- continue;
102
- }
103
- if (node === workInProgress) {
104
- return;
105
- }
106
- while (node.sibling === null) {
107
- if (node.return === null || node.return === workInProgress) {
108
- return;
109
- }
110
- node = node.return;
111
- }
112
- node.sibling.return = node.return;
113
- node = node.sibling;
114
- }
115
-}
116
-
91
+let appendAllChildren;
92
let updateHostContainer;
93
let updateHostComponent;
94
let updateHostText;
95
if (supportsMutation) {
96
// Mutation mode
97
98
+ appendAllChildren = function(
99
+ parent: Instance,
100
+ workInProgress: Fiber,
101
+ needsVisibilityToggle: boolean,
102
+ isHidden: boolean,
103
+ ) {
104
+ // We only have the top Fiber that was created but we need recurse down its
105
+ // children to find all the terminal nodes.
106
+ let node = workInProgress.child;
107
+ while (node !== null) {
108
+ if (node.tag === HostComponent || node.tag === HostText) {
109
+ appendInitialChild(parent, node.stateNode);
110
+ } else if (node.tag === HostPortal) {
111
+ // If we have a portal child, then we don't want to traverse
112
+ // down its children. Instead, we'll get insertions from each child in
113
+ // the portal directly.
114
+ } else if (node.child !== null) {
115
+ node.child.return = node;
116
+ node = node.child;
117
+ continue;
118
+ }
119
+ if (node === workInProgress) {
120
+ return;
121
+ }
122
+ while (node.sibling === null) {
123
+ if (node.return === null || node.return === workInProgress) {
124
+ return;
125
+ }
126
+ node = node.return;
127
+ }
128
+ node.sibling.return = node.return;
129
+ node = node.sibling;
130
+ }
131
+ };
132
+
133
updateHostContainer = function(workInProgress: Fiber) {
134
// Noop
135
};
188
} else if (supportsPersistence) {
189
// Persistent host tree mode
190
191
+ appendAllChildren = function(
192
+ parent: Instance,
193
+ workInProgress: Fiber,
194
+ needsVisibilityToggle: boolean,
195
+ isHidden: boolean,
196
+ ) {
197
+ // We only have the top Fiber that was created but we need recurse down its
198
+ // children to find all the terminal nodes.
199
+ let node = workInProgress.child;
200
+ while (node !== null) {
201
+ // eslint-disable-next-line no-labels
202
+ branches: if (node.tag === HostComponent) {
203
+ let instance = node.stateNode;
204
+ if (needsVisibilityToggle) {
205
+ const props = node.memoizedProps;
206
+ const type = node.type;
207
+ if (isHidden) {
208
+ // This child is inside a timed out tree. Hide it.
209
+ instance = cloneHiddenInstance(instance, type, props, node);
210
+ } else {
211
+ // This child was previously inside a timed out tree. If it was not
212
+ // updated during this render, it may need to be unhidden. Clone
213
+ // again to be sure.
214
+ instance = cloneUnhiddenInstance(instance, type, props, node);
215
+ }
216
+ node.stateNode = instance;
217
+ }
218
+ appendInitialChild(parent, instance);
219
+ } else if (node.tag === HostText) {
220
+ let instance = node.stateNode;
221
+ if (needsVisibilityToggle) {
222
+ const text = node.memoizedProps;
223
+ const rootContainerInstance = getRootHostContainer();
224
+ const currentHostContext = getHostContext();
225
+ if (isHidden) {
226
+ instance = createHiddenTextInstance(
227
+ text,
228
+ rootContainerInstance,
229
+ currentHostContext,
230
+ workInProgress,
231
+ );
232
+ } else {
233
+ instance = createTextInstance(
234
+ text,
235
+ rootContainerInstance,
236
+ currentHostContext,
237
+ workInProgress,
238
+ );
239
+ }
240
+ node.stateNode = instance;
241
+ }
242
+ appendInitialChild(parent, instance);
243
+ } else if (node.tag === HostPortal) {
244
+ // If we have a portal child, then we don't want to traverse
245
+ // down its children. Instead, we'll get insertions from each child in
246
+ // the portal directly.
247
+ } else if (node.tag === SuspenseComponent) {
248
+ const current = node.alternate;
249
+ if (current !== null) {
250
+ const oldState: SuspenseState = current.memoizedState;
251
+ const newState: SuspenseState = node.memoizedState;
252
+ const oldIsHidden = oldState !== null && oldState.didTimeout;
253
+ const newIsHidden = newState !== null && newState.didTimeout;
254
+ if (oldIsHidden !== newIsHidden) {
255
+ // The placeholder either just timed out or switched back to the normal
256
+ // children after having previously timed out. Toggle the visibility of
257
+ // the direct host children.
258
+ const primaryChildParent = newIsHidden ? node.child : node;
259
+ if (primaryChildParent !== null) {
260
+ appendAllChildren(parent, primaryChildParent, true, newIsHidden);
261
+ }
262
+ // eslint-disable-next-line no-labels
263
+ break branches;
264
+ }
265
+ }
266
+ if (node.child !== null) {
267
+ // Continue traversing like normal
268
+ node.child.return = node;
269
+ node = node.child;
270
+ continue;
271
+ }
272
+ } else if (node.child !== null) {
273
+ node.child.return = node;
274
+ node = node.child;
275
+ continue;
276
+ }
277
+ // $FlowFixMe This is correct but Flow is confused by the labeled break.
278
+ node = (node: Fiber);
279
+ if (node === workInProgress) {
280
+ return;
281
+ }
282
+ while (node.sibling === null) {
283
+ if (node.return === null || node.return === workInProgress) {
284
+ return;
285
+ }
286
+ node = node.return;
287
+ }
288
+ node.sibling.return = node.return;
289
+ node = node.sibling;
290
+ }
291
+ };
292
+
293
// An unfortunate fork of appendAllChildren because we have two different parent types.
294
const appendAllChildrenToContainer = function(
295
containerChildSet: ChildSet,
296
workInProgress: Fiber,
297
+ needsVisibilityToggle: boolean,
298
+ isHidden: boolean,
299
) {
300
// We only have the top Fiber that was created but we need recurse down its
301
// children to find all the terminal nodes.
302
let node = workInProgress.child;
303
while (node !== null) {
190
- if (node.tag === HostComponent || node.tag === HostText) {
191
- appendChildToContainerChildSet(containerChildSet, node.stateNode);
304
+ // eslint-disable-next-line no-labels
305
+ branches: if (node.tag === HostComponent) {
306
+ let instance = node.stateNode;
307
+ if (needsVisibilityToggle) {
308
+ const props = node.memoizedProps;
309
+ const type = node.type;
310
+ if (isHidden) {
311
+ // This child is inside a timed out tree. Hide it.
312
+ instance = cloneHiddenInstance(instance, type, props, node);
313
+ } else {
314
+ // This child was previously inside a timed out tree. If it was not
315
+ // updated during this render, it may need to be unhidden. Clone
316
+ // again to be sure.
317
+ instance = cloneUnhiddenInstance(instance, type, props, node);
318
+ }
319
+ node.stateNode = instance;
320
+ }
321
+ appendChildToContainerChildSet(containerChildSet, instance);
322
+ } else if (node.tag === HostText) {
323
+ let instance = node.stateNode;
324
+ if (needsVisibilityToggle) {
325
+ const text = node.memoizedProps;
326
+ const rootContainerInstance = getRootHostContainer();
327
+ const currentHostContext = getHostContext();
328
+ if (isHidden) {
329
+ instance = createHiddenTextInstance(
330
+ text,
331
+ rootContainerInstance,
332
+ currentHostContext,
333
+ workInProgress,
334
+ );
335
+ } else {
336
+ instance = createTextInstance(
337
+ text,
338
+ rootContainerInstance,
339
+ currentHostContext,
340
+ workInProgress,
341
+ );
342
+ }
343
+ node.stateNode = instance;
344
+ }
345
+ appendChildToContainerChildSet(containerChildSet, instance);
346
} else if (node.tag === HostPortal) {
347
// If we have a portal child, then we don't want to traverse
348
// down its children. Instead, we'll get insertions from each child in
349
// the portal directly.
350
+ } else if (node.tag === SuspenseComponent) {
351
+ const current = node.alternate;
352
+ if (current !== null) {
353
+ const oldState: SuspenseState = current.memoizedState;
354
+ const newState: SuspenseState = node.memoizedState;
355
+ const oldIsHidden = oldState !== null && oldState.didTimeout;
356
+ const newIsHidden = newState !== null && newState.didTimeout;
357
+ if (oldIsHidden !== newIsHidden) {
358
+ // The placeholder either just timed out or switched back to the normal
359
+ // children after having previously timed out. Toggle the visibility of
360
+ // the direct host children.
361
+ const primaryChildParent = newIsHidden ? node.child : node;
362
+ if (primaryChildParent !== null) {
363
+ appendAllChildrenToContainer(
364
+ containerChildSet,
365
+ primaryChildParent,
366
+ true,
367
+ newIsHidden,
368
+ );
369
+ }
370
+ // eslint-disable-next-line no-labels
371
+ break branches;
372
+ }
373
+ }
374
+ if (node.child !== null) {
375
+ // Continue traversing like normal
376
+ node.child.return = node;
377
+ node = node.child;
378
+ continue;
379
+ }
380
} else if (node.child !== null) {
381
node.child.return = node;
382
node = node.child;
383
continue;
384
}
385
+ // $FlowFixMe This is correct but Flow is confused by the labeled break.
386
+ node = (node: Fiber);
387
if (node === workInProgress) {
388
return;
389
}
410
const container = portalOrRoot.containerInfo;
411
let newChildSet = createContainerChildSet(container);
412
// If children might have changed, we have to add them all to the set.
227
- appendAllChildrenToContainer(newChildSet, workInProgress);
413
+ appendAllChildrenToContainer(newChildSet, workInProgress, false, false);
414
portalOrRoot.pendingChildren = newChildSet;
415
// Schedule an update on the container to swap out the container.
416
markUpdate(workInProgress);
483
markUpdate(workInProgress);
484
} else {
485
// If children might have changed, we have to add them all to the set.
300
- appendAllChildren(newInstance, workInProgress);
486
+ appendAllChildren(newInstance, workInProgress, false, false);
487
}
488
};
489
updateHostText = function(
539
const newProps = workInProgress.pendingProps;
540
541
switch (workInProgress.tag) {
542
+ case IndeterminateComponent:
543
+ break;
544
case FunctionComponent:
545
case FunctionComponentLazy:
546
break;
633
workInProgress,
634
);
635
448
- appendAllChildren(instance, workInProgress);
636
+ appendAllChildren(instance, workInProgress, false, false);
637
638
// Certain renderers require commit-time effects for initial mount.
639
// (eg DOM renderer supports auto-focus for certain elements).
696
case ForwardRef:
697
case ForwardRefLazy:
698
break;
511
- case SuspenseComponent:
699
+ case SuspenseComponent: {
700
+ const nextState = workInProgress.memoizedState;
701
+ const prevState = current !== null ? current.memoizedState : null;
702
+ const nextDidTimeout = nextState !== null && nextState.didTimeout;
703
+ const prevDidTimeout = prevState !== null && prevState.didTimeout;
704
+ if (nextDidTimeout !== prevDidTimeout) {
705
+ // If this render commits, and it switches between the normal state
706
+ // and the timed-out state, schedule an effect.
707
+ workInProgress.effectTag |= Update;
708
+ }
709
break;
710
+ }
711
case Fragment:
712
break;
713
case Mode:
727
case PureComponent:
728
case PureComponentLazy:
729
break;
532
- // Error cases
533
- case IndeterminateComponent:
534
- invariant(
535
- false,
536
- 'An indeterminate component should have become determinate before ' +
537
- 'completing. This error is likely caused by a bug in React. Please ' +
538
- 'file an issue.',
539
- );
540
- // eslint-disable-next-line no-fallthrough
730
default:
731
invariant(
732
false,