@@ -67,7 +67,7 @@ describe('ReactFlightDOMForm', () => {
6767 webpackServerMap ,
6868 ) ;
6969 const returnValue = boundAction ( ) ;
70- const formState = ReactServerDOMServer . decodeFormState (
70+ const formState = await ReactServerDOMServer . decodeFormState (
7171 await returnValue ,
7272 formData ,
7373 webpackServerMap ,
@@ -435,6 +435,174 @@ describe('ReactFlightDOMForm', () => {
435435 }
436436 } ) ;
437437
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+
438606 // @gate enableFormActions
439607 // @gate enableAsyncActions
440608 it ( 'useFormState can change the action URL with the `permalink` argument' , async ( ) => {
0 commit comments