@@ -1438,6 +1438,12 @@ const IsScheduling = 1 << 10;
1438
1438
*/
1439
1439
const IsSchedulingRefresh = 1 << 11 ;
1440
1440
1441
+ /**
1442
+ * A flag which is set when a component has yielded and needs new props. This
1443
+ * flag is used to determine if a component is yielding with stale props.
1444
+ */
1445
+ const NeedsNewProps = 1 << 12 ;
1446
+
1441
1447
export interface Context extends Crank . Context { }
1442
1448
1443
1449
/**
@@ -1612,10 +1618,12 @@ export class Context<T = any, TResult = any> implements EventTarget {
1612
1618
ctx . f |= NeedsToYield ;
1613
1619
}
1614
1620
1621
+ ctx . f &= ~ NeedsNewProps ;
1615
1622
yield ctx . ret . el . props as ComponentProps < T > ;
1616
1623
}
1617
1624
} finally {
1618
1625
ctx . f &= ~ IsInForOfLoop ;
1626
+ ctx . f &= ~ NeedsNewProps ;
1619
1627
}
1620
1628
}
1621
1629
@@ -2246,7 +2254,7 @@ function enqueueComponentRun<TNode, TResult>(
2246
2254
// the bottom of the loop to be reached before returning the inflight
2247
2255
// value.
2248
2256
const isAtLoopbottom = ctx . f & IsInForAwaitOfLoop && ! ctx . onProps ;
2249
- resumePropsIterator ( ctx ) ;
2257
+ resumePropsAsyncIterator ( ctx ) ;
2250
2258
if ( isAtLoopbottom ) {
2251
2259
if ( ctx . inflightBlock == null ) {
2252
2260
ctx . inflightBlock = new Promise (
@@ -2361,7 +2369,7 @@ function runComponent<TNode, TResult>(
2361
2369
const ret = ctx . ret ;
2362
2370
const initial = ! ctx . iterator ;
2363
2371
if ( initial ) {
2364
- resumePropsIterator ( ctx ) ;
2372
+ resumePropsAsyncIterator ( ctx ) ;
2365
2373
ctx . f |= IsSyncExecuting ;
2366
2374
clearEventListeners ( ctx ) ;
2367
2375
let result : ReturnType < Component > ;
@@ -2401,6 +2409,7 @@ function runComponent<TNode, TResult>(
2401
2409
] ;
2402
2410
}
2403
2411
} else if ( hydrationData !== undefined ) {
2412
+ // hydration data should only be passed on the initial render
2404
2413
throw new Error ( "Hydration error" ) ;
2405
2414
}
2406
2415
@@ -2438,6 +2447,12 @@ function runComponent<TNode, TResult>(
2438
2447
}
2439
2448
}
2440
2449
2450
+ if ( ctx . f & NeedsNewProps && ! ( ctx . f & IsUnmounted ) ) {
2451
+ console . error ( "Component yielded twice without accessing new props" ) ;
2452
+ } else if ( ctx . f & IsInForOfLoop ) {
2453
+ ctx . f |= NeedsNewProps ;
2454
+ }
2455
+
2441
2456
if ( isPromiseLike ( iteration ) ) {
2442
2457
throw new Error ( "Mixed generator component" ) ;
2443
2458
}
@@ -2466,7 +2481,7 @@ function runComponent<TNode, TResult>(
2466
2481
const block = isPromiseLike ( value ) ? value . catch ( NOOP ) : undefined ;
2467
2482
return [ block , value ] ;
2468
2483
} else if ( ctx . f & IsInForOfLoop ) {
2469
- // TODO: does this need to be done async?
2484
+ // Async generator component using for...of loop
2470
2485
ctx . f &= ~ NeedsToYield ;
2471
2486
// we are in a for...of loop for async generator
2472
2487
if ( ! initial ) {
@@ -2488,6 +2503,12 @@ function runComponent<TNode, TResult>(
2488
2503
const block = iteration . catch ( NOOP ) ;
2489
2504
const value = iteration . then (
2490
2505
( iteration ) => {
2506
+ if ( ctx . f & NeedsNewProps && ! ( ctx . f & IsUnmounted ) ) {
2507
+ console . error ( "Component yielded twice without accessing new props" ) ;
2508
+ } else if ( ctx . f & IsInForOfLoop ) {
2509
+ ctx . f |= NeedsNewProps ;
2510
+ }
2511
+
2491
2512
let value : Promise < ElementValue < TNode > > | ElementValue < TNode > ;
2492
2513
if ( ! ( ctx . f & IsInForOfLoop ) ) {
2493
2514
runAsyncGenComponent ( ctx , Promise . resolve ( iteration ) , hydrationData ) ;
@@ -2648,7 +2669,7 @@ async function runAsyncGenComponent<TNode, TResult>(
2648
2669
/**
2649
2670
* Called to resume the props async iterator for async generator components.
2650
2671
*/
2651
- function resumePropsIterator ( ctx : ContextImpl ) : void {
2672
+ function resumePropsAsyncIterator ( ctx : ContextImpl ) : void {
2652
2673
if ( ctx . onProps ) {
2653
2674
ctx . onProps ( ctx . ret . el . props ) ;
2654
2675
ctx . onProps = undefined ;
@@ -2728,14 +2749,14 @@ function unmountComponent(ctx: ContextImpl): void {
2728
2749
} else {
2729
2750
// The logic for unmounting async generator components is in the
2730
2751
// runAsyncGenComponent function.
2731
- resumePropsIterator ( ctx ) ;
2752
+ resumePropsAsyncIterator ( ctx ) ;
2732
2753
}
2733
2754
}
2734
2755
}
2735
2756
}
2736
2757
2737
2758
function returnComponent ( ctx : ContextImpl ) : void {
2738
- resumePropsIterator ( ctx ) ;
2759
+ resumePropsAsyncIterator ( ctx ) ;
2739
2760
if ( ctx . iterator && typeof ctx . iterator ! . return === "function" ) {
2740
2761
try {
2741
2762
ctx . f |= IsSyncExecuting ;
@@ -2883,7 +2904,7 @@ function handleChildError<TNode>(
2883
2904
throw err ;
2884
2905
}
2885
2906
2886
- resumePropsIterator ( ctx ) ;
2907
+ resumePropsAsyncIterator ( ctx ) ;
2887
2908
let iteration : ChildrenIteratorResult | Promise < ChildrenIteratorResult > ;
2888
2909
try {
2889
2910
ctx . f |= IsSyncExecuting ;
0 commit comments