98
} from './ReactFizzNewContext';
99
import {
100
prepareToUseHooks,
101
+ prepareToUseThenableState,
102
finishHooks,
103
checkDidRenderIdHook,
104
resetHooksState,
107
setCurrentResumableState,
108
getThenableStateAfterSuspending,
109
unwrapThenable,
110
+ readPreviousThenableFromState,
111
getActionStateCount,
112
getActionStateMatchingIndex,
113
} from './ReactFizzHooks';
117
118
import {
119
getIteratorFn,
120
+ ASYNC_ITERATOR,
121
REACT_ELEMENT_TYPE,
122
REACT_PORTAL_TYPE,
123
REACT_LAZY_TYPE,
147
enableRenderableContext,
148
enableRefAsProp,
149
disableDefaultPropsExceptForClasses,
150
+ enableAsyncIterableChildren,
151
} from 'shared/ReactFeatureFlags';
152
153
import assign from 'shared/assign';
2169
// as its direct child since we can recreate those by rerendering the component
2170
// as needed.
2171
const isGeneratorComponent =
2172
+ childIndex === -1 && // Only the root child is valid
2173
task.componentStack !== null &&
2174
task.componentStack.tag === 1 && // FunctionComponent
2175
// $FlowFixMe[method-unbinding]
2202
}
2203
}
2204
2205
+function validateAsyncIterable(
2206
+ task: Task,
2207
+ iterable: AsyncIterable<any>,
2208
+ childIndex: number,
2209
+ iterator: AsyncIterator<any>,
2210
+): void {
2211
+ if (__DEV__) {
2212
+ if (iterator === iterable) {
2213
+ // We don't support rendering Generators as props because it's a mutation.
2214
+ // See https://github.com/facebook/react/issues/12995
2215
+ // We do support generators if they were created by a GeneratorFunction component
2216
+ // as its direct child since we can recreate those by rerendering the component
2217
+ // as needed.
2218
+ const isGeneratorComponent =
2219
+ childIndex === -1 && // Only the root child is valid
2220
+ task.componentStack !== null &&
2221
+ task.componentStack.tag === 1 && // FunctionComponent
2222
+ // $FlowFixMe[method-unbinding]
2223
+ Object.prototype.toString.call(task.componentStack.type) ===
2224
+ '[object AsyncGeneratorFunction]' &&
2225
+ // $FlowFixMe[method-unbinding]
2226
+ Object.prototype.toString.call(iterator) === '[object AsyncGenerator]';
2227
+ if (!isGeneratorComponent) {
2228
+ if (!didWarnAboutGenerators) {
2229
+ console.error(
2230
+ 'Using AsyncIterators as children is unsupported and will likely yield ' +
2231
+ 'unexpected results because enumerating a generator mutates it. ' +
2232
+ 'You can use an AsyncIterable that can iterate multiple times over ' +
2233
+ 'the same items.',
2234
+ );
2235
+ }
2236
+ didWarnAboutGenerators = true;
2237
+ }
2238
+ }
2239
+ }
2240
+}
2241
+
2242
function warnOnFunctionType(invalidChild: Function) {
2243
if (__DEV__) {
2244
const name = invalidChild.displayName || invalidChild.name || 'Component';
2369
// TODO: This is not great but I think it's inherent to the id
2370
// generation algorithm.
2371
let step = iterator.next();
2330
- // If there are not entries, we need to push an empty so we start by checking that.
2372
if (!step.done) {
2373
const children = [];
2374
do {
2376
step = iterator.next();
2377
} while (!step.done);
2378
renderChildrenArray(request, task, children, childIndex);
2338
- return;
2379
}
2380
return;
2381
}
2382
}
2383
2384
+ if (
2385
+ enableAsyncIterableChildren &&
2386
+ typeof (node: any)[ASYNC_ITERATOR] === 'function'
2387
+ ) {
2388
+ const iterator: AsyncIterator<ReactNodeList> = (node: any)[
2389
+ ASYNC_ITERATOR
2390
+ ]();
2391
+ if (iterator) {
2392
+ if (__DEV__) {
2393
+ validateAsyncIterable(task, (node: any), childIndex, iterator);
2394
+ }
2395
+ // TODO: Update the task.node to be the iterator to avoid asking
2396
+ // for new iterators, but we currently warn for rendering these
2397
+ // so needs some refactoring to deal with the warning.
2398
+
2399
+ // We need to push a component stack because if this suspends, we'll pop a stack.
2400
+ const previousComponentStack = task.componentStack;
2401
+ task.componentStack = createBuiltInComponentStack(
2402
+ task,
2403
+ 'AsyncIterable',
2404
+ );
2405
+
2406
+ // Restore the thenable state before resuming.
2407
+ const prevThenableState = task.thenableState;
2408
+ task.thenableState = null;
2409
+ prepareToUseThenableState(prevThenableState);
2410
+
2411
+ // We need to know how many total children are in this set, so that we
2412
+ // can allocate enough id slots to acommodate them. So we must exhaust
2413
+ // the iterator before we start recursively rendering the children.
2414
+ // TODO: This is not great but I think it's inherent to the id
2415
+ // generation algorithm.
2416
+ const children = [];
2417
+
2418
+ let done = false;
2419
+
2420
+ if (iterator === node) {
2421
+ // If it's an iterator we need to continue reading where we left
2422
+ // off. We can do that by reading the first few rows from the previous
2423
+ // thenable state.
2424
+ // $FlowFixMe
2425
+ let step = readPreviousThenableFromState();
2426
+ while (step !== undefined) {
2427
+ if (step.done) {
2428
+ done = true;
2429
+ break;
2430
+ }
2431
+ children.push(step.value);
2432
+ step = readPreviousThenableFromState();
2433
+ }
2434
+ }
2435
+
2436
+ if (!done) {
2437
+ let step = unwrapThenable(iterator.next());
2438
+ while (!step.done) {
2439
+ children.push(step.value);
2440
+ step = unwrapThenable(iterator.next());
2441
+ }
2442
+ }
2443
+ task.componentStack = previousComponentStack;
2444
+ renderChildrenArray(request, task, children, childIndex);
2445
+ return;
2446
+ }
2447
+ }
2448
+
2449
// Usables are a valid React node type. When React encounters a Usable in
2450
// a child position, it unwraps it using the same algorithm as `use`. For
2451
// example, for promises, React will throw an exception to unwind the
3659
const ping = task.ping;
3660
x.then(ping, ping);
3661
task.thenableState = getThenableStateAfterSuspending();
3662
+ // We pop one task off the stack because the node that suspended will be tried again,
3663
+ // which will add it back onto the stack.
3664
+ if (task.componentStack !== null) {
3665
+ task.componentStack = task.componentStack.parent;
3666
+ }
3667
return;
3668
} else if (
3669
enablePostpone &&
3749
const ping = task.ping;
3750
x.then(ping, ping);
3751
task.thenableState = getThenableStateAfterSuspending();
3752
+ // We pop one task off the stack because the node that suspended will be tried again,
3753
+ // which will add it back onto the stack.
3754
+ if (task.componentStack !== null) {
3755
+ task.componentStack = task.componentStack.parent;
3756
+ }
3757
return;
3758
}
3759
}