@@ -67,7 +67,7 @@ describe('ReactFlightDOMForm', () => {
67
67
webpackServerMap ,
68
68
) ;
69
69
const returnValue = boundAction ( ) ;
70
- const formState = ReactServerDOMServer . decodeFormState (
70
+ const formState = await ReactServerDOMServer . decodeFormState (
71
71
await returnValue ,
72
72
formData ,
73
73
webpackServerMap ,
@@ -435,6 +435,174 @@ describe('ReactFlightDOMForm', () => {
435
435
}
436
436
} ) ;
437
437
438
+ // @gate enableFormActions
439
+ // @gate enableAsyncActions
440
+ it (
441
+ 'useFormState preserves state if arity is the same, but different ' +
442
+ 'arguments are bound (i.e. inline closure)' ,
443
+ async ( ) => {
444
+ const serverAction = serverExports ( async function action (
445
+ stepSize ,
446
+ prevState ,
447
+ formData ,
448
+ ) {
449
+ return prevState + stepSize ;
450
+ } ) ;
451
+
452
+ function Form ( { action} ) {
453
+ const [ count , dispatch ] = useFormState ( action , 1 ) ;
454
+ return < form action = { dispatch } > { count } </ form > ;
455
+ }
456
+
457
+ function Client ( { action} ) {
458
+ return (
459
+ < div >
460
+ < Form action = { action } />
461
+ < Form action = { action } />
462
+ < Form action = { action } />
463
+ </ div >
464
+ ) ;
465
+ }
466
+
467
+ const ClientRef = await clientExports ( Client ) ;
468
+
469
+ const rscStream = ReactServerDOMServer . renderToReadableStream (
470
+ // Note: `.bind` is the same as an inline closure with 'use server'
471
+ < ClientRef action = { serverAction . bind ( null , 1 ) } /> ,
472
+ webpackMap ,
473
+ ) ;
474
+ const response = ReactServerDOMClient . createFromReadableStream ( rscStream ) ;
475
+ const ssrStream = await ReactDOMServer . renderToReadableStream ( response ) ;
476
+ await readIntoContainer ( ssrStream ) ;
477
+
478
+ expect ( container . textContent ) . toBe ( '111' ) ;
479
+
480
+ // There are three identical forms. We're going to submit the second one.
481
+ const form = container . getElementsByTagName ( 'form' ) [ 1 ] ;
482
+ const { formState} = await submit ( form ) ;
483
+
484
+ // Simulate an MPA form submission by resetting the container and
485
+ // rendering again.
486
+ container . innerHTML = '' ;
487
+
488
+ // On the next page, the same server action is rendered again, but with
489
+ // a different bound stepSize argument. We should treat this as the same
490
+ // action signature.
491
+ const postbackRscStream = ReactServerDOMServer . renderToReadableStream (
492
+ // Note: `.bind` is the same as an inline closure with 'use server'
493
+ < ClientRef action = { serverAction . bind ( null , 5 ) } /> ,
494
+ webpackMap ,
495
+ ) ;
496
+ const postbackResponse =
497
+ ReactServerDOMClient . createFromReadableStream ( postbackRscStream ) ;
498
+ const postbackSsrStream = await ReactDOMServer . renderToReadableStream (
499
+ postbackResponse ,
500
+ { experimental_formState : formState } ,
501
+ ) ;
502
+ await readIntoContainer ( postbackSsrStream ) ;
503
+
504
+ // The state should have been preserved because the action signatures are
505
+ // the same. (Note that the amount increased by 1, because that was the
506
+ // value of stepSize at the time the form was submitted)
507
+ expect ( container . textContent ) . toBe ( '121' ) ;
508
+
509
+ // Now submit the form again. This time, the state should increase by 5
510
+ // because the stepSize argument has changed.
511
+ const form2 = container . getElementsByTagName ( 'form' ) [ 1 ] ;
512
+ const { formState : formState2 } = await submit ( form2 ) ;
513
+
514
+ container . innerHTML = '' ;
515
+
516
+ const postbackRscStream2 = ReactServerDOMServer . renderToReadableStream (
517
+ // Note: `.bind` is the same as an inline closure with 'use server'
518
+ < ClientRef action = { serverAction . bind ( null , 5 ) } /> ,
519
+ webpackMap ,
520
+ ) ;
521
+ const postbackResponse2 =
522
+ ReactServerDOMClient . createFromReadableStream ( postbackRscStream2 ) ;
523
+ const postbackSsrStream2 = await ReactDOMServer . renderToReadableStream (
524
+ postbackResponse2 ,
525
+ { experimental_formState : formState2 } ,
526
+ ) ;
527
+ await readIntoContainer ( postbackSsrStream2 ) ;
528
+
529
+ expect ( container . textContent ) . toBe ( '171' ) ;
530
+ } ,
531
+ ) ;
532
+
533
+ // @gate enableFormActions
534
+ // @gate enableAsyncActions
535
+ it ( 'useFormState does not reuse state if action signatures are different' , async ( ) => {
536
+ // This is the same as the previous test, except instead of using bind to
537
+ // configure the server action (i.e. a closure), it swaps the action.
538
+ const increaseBy1 = serverExports ( async function action (
539
+ prevState ,
540
+ formData ,
541
+ ) {
542
+ return prevState + 1 ;
543
+ } ) ;
544
+
545
+ const increaseBy5 = serverExports ( async function action (
546
+ prevState ,
547
+ formData ,
548
+ ) {
549
+ return prevState + 5 ;
550
+ } ) ;
551
+
552
+ function Form ( { action} ) {
553
+ const [ count , dispatch ] = useFormState ( action , 1 ) ;
554
+ return < form action = { dispatch } > { count } </ form > ;
555
+ }
556
+
557
+ function Client ( { action} ) {
558
+ return (
559
+ < div >
560
+ < Form action = { action } />
561
+ < Form action = { action } />
562
+ < Form action = { action } />
563
+ </ div >
564
+ ) ;
565
+ }
566
+
567
+ const ClientRef = await clientExports ( Client ) ;
568
+
569
+ const rscStream = ReactServerDOMServer . renderToReadableStream (
570
+ < ClientRef action = { increaseBy1 } /> ,
571
+ webpackMap ,
572
+ ) ;
573
+ const response = ReactServerDOMClient . createFromReadableStream ( rscStream ) ;
574
+ const ssrStream = await ReactDOMServer . renderToReadableStream ( response ) ;
575
+ await readIntoContainer ( ssrStream ) ;
576
+
577
+ expect ( container . textContent ) . toBe ( '111' ) ;
578
+
579
+ // There are three identical forms. We're going to submit the second one.
580
+ const form = container . getElementsByTagName ( 'form' ) [ 1 ] ;
581
+ const { formState} = await submit ( form ) ;
582
+
583
+ // Simulate an MPA form submission by resetting the container and
584
+ // rendering again.
585
+ container . innerHTML = '' ;
586
+
587
+ // On the next page, a different server action is rendered. It should not
588
+ // reuse the state from the previous page.
589
+ const postbackRscStream = ReactServerDOMServer . renderToReadableStream (
590
+ < ClientRef action = { increaseBy5 } /> ,
591
+ webpackMap ,
592
+ ) ;
593
+ const postbackResponse =
594
+ ReactServerDOMClient . createFromReadableStream ( postbackRscStream ) ;
595
+ const postbackSsrStream = await ReactDOMServer . renderToReadableStream (
596
+ postbackResponse ,
597
+ { experimental_formState : formState } ,
598
+ ) ;
599
+ await readIntoContainer ( postbackSsrStream ) ;
600
+
601
+ // The state should not have been preserved because the action signatures
602
+ // are not the same.
603
+ expect ( container . textContent ) . toBe ( '111' ) ;
604
+ } ) ;
605
+
438
606
// @gate enableFormActions
439
607
// @gate enableAsyncActions
440
608
it ( 'useFormState can change the action URL with the `permalink` argument' , async ( ) => {
0 commit comments