@@ -98,6 +98,7 @@ import {
98
98
} from './ReactFizzNewContext' ;
99
99
import {
100
100
prepareToUseHooks ,
101
+ prepareToUseThenableState ,
101
102
finishHooks ,
102
103
checkDidRenderIdHook ,
103
104
resetHooksState ,
@@ -106,6 +107,7 @@ import {
106
107
setCurrentResumableState ,
107
108
getThenableStateAfterSuspending ,
108
109
unwrapThenable ,
110
+ readPreviousThenableFromState ,
109
111
getActionStateCount ,
110
112
getActionStateMatchingIndex ,
111
113
} from './ReactFizzHooks' ;
@@ -115,6 +117,7 @@ import {emptyTreeContext, pushTreeContext} from './ReactFizzTreeContext';
115
117
116
118
import {
117
119
getIteratorFn ,
120
+ ASYNC_ITERATOR ,
118
121
REACT_ELEMENT_TYPE ,
119
122
REACT_PORTAL_TYPE ,
120
123
REACT_LAZY_TYPE ,
@@ -144,6 +147,7 @@ import {
144
147
enableRenderableContext ,
145
148
enableRefAsProp ,
146
149
disableDefaultPropsExceptForClasses ,
150
+ enableAsyncIterableChildren ,
147
151
} from 'shared/ReactFeatureFlags' ;
148
152
149
153
import assign from 'shared/assign' ;
@@ -2165,6 +2169,7 @@ function validateIterable(
2165
2169
// as its direct child since we can recreate those by rerendering the component
2166
2170
// as needed.
2167
2171
const isGeneratorComponent =
2172
+ childIndex === - 1 && // Only the root child is valid
2168
2173
task . componentStack !== null &&
2169
2174
task . componentStack . tag === 1 && // FunctionComponent
2170
2175
// $FlowFixMe[method-unbinding]
@@ -2197,6 +2202,43 @@ function validateIterable(
2197
2202
}
2198
2203
}
2199
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
+
2200
2242
function warnOnFunctionType ( invalidChild : Function ) {
2201
2243
if ( __DEV__ ) {
2202
2244
const name = invalidChild . displayName || invalidChild . name || 'Component' ;
@@ -2327,20 +2369,83 @@ function renderNodeDestructive(
2327
2369
// TODO: This is not great but I think it's inherent to the id
2328
2370
// generation algorithm.
2329
2371
let step = iterator . next ( ) ;
2330
- // If there are not entries, we need to push an empty so we start by checking that.
2331
2372
if ( ! step . done ) {
2332
2373
const children = [ ] ;
2333
2374
do {
2334
2375
children . push ( step . value ) ;
2335
2376
step = iterator . next ( ) ;
2336
2377
} while ( ! step . done ) ;
2337
2378
renderChildrenArray ( request , task , children , childIndex ) ;
2338
- return ;
2339
2379
}
2340
2380
return ;
2341
2381
}
2342
2382
}
2343
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
+
2344
2449
// Usables are a valid React node type. When React encounters a Usable in
2345
2450
// a child position, it unwraps it using the same algorithm as `use`. For
2346
2451
// example, for promises, React will throw an exception to unwind the
@@ -3554,6 +3659,11 @@ function retryRenderTask(
3554
3659
const ping = task . ping ;
3555
3660
x . then ( ping , ping ) ;
3556
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
+ }
3557
3667
return ;
3558
3668
} else if (
3559
3669
enablePostpone &&
@@ -3639,6 +3749,11 @@ function retryReplayTask(request: Request, task: ReplayTask): void {
3639
3749
const ping = task . ping ;
3640
3750
x . then ( ping , ping ) ;
3641
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
+ }
3642
3757
return ;
3643
3758
}
3644
3759
}
0 commit comments