67
webpackServerMap,
68
);
69
const returnValue = boundAction();
70
- const formState = ReactServerDOMServer.decodeFormState(
70
+ const formState = await ReactServerDOMServer.decodeFormState(
71
await returnValue,
72
formData,
73
webpackServerMap,
435
}
436
});
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
+
606
// @gate enableFormActions
607
// @gate enableAsyncActions
608
it('useFormState can change the action URL with the `permalink` argument', async () => {