586
return [passthrough, unsupportedSetOptimisticState];
587
}
588
589
+function createPostbackFormStateKey(
590
+ permalink: string | void,
591
+ componentKeyPath: KeyNode | null,
592
+ hookIndex: number,
593
+): string {
594
+ if (permalink !== undefined) {
595
+ return 'p' + permalink;
596
+ } else {
597
+ // Append a node to the key path that represents the form state hook.
598
+ const keyPath: KeyNode = [componentKeyPath, null, hookIndex];
599
+ return 'k' + JSON.stringify(keyPath);
600
+ }
601
+}
602
+
603
function useFormState<S, P>(
604
action: (S, P) => Promise<S>,
605
initialState: S,
619
// This is a server action. These have additional features to enable
620
// MPA-style form submissions with progressive enhancement.
621
622
+ // TODO: If the same permalink is passed to multiple useFormStates, and
623
+ // they all have the same action signature, Fizz will pass the postback
624
+ // state to all of them. We should probably only pass it to the first one,
625
+ // and/or warn.
626
+
627
+ // The key is lazily generated and deduped so the that the keypath doesn't
628
+ // get JSON.stringify-ed unnecessarily, and at most once.
629
+ let nextPostbackStateKey = null;
630
+
631
// Determine the current form state. If we received state during an MPA form
632
// submission, then we will reuse that, if the action identity matches.
633
// Otherwise we'll use the initial state argument. We will emit a comment
634
// marker into the stream that indicates whether the state was reused.
635
let state = initialState;
613
-
614
- // Append a node to the key path that represents the form state hook.
615
- const componentKey: KeyNode | null = (currentlyRenderingKeyPath: any);
616
- const key: KeyNode = [componentKey, null, formStateHookIndex];
617
- const keyJSON = JSON.stringify(key);
618
-
636
+ const componentKeyPath = (currentlyRenderingKeyPath: any);
637
const postbackFormState = getFormState(request);
638
// $FlowIgnore[prop-missing]
639
const isSignatureEqual = action.$$IS_SIGNATURE_EQUAL;
640
if (postbackFormState !== null && typeof isSignatureEqual === 'function') {
623
- const postbackKeyJSON = postbackFormState[1];
641
+ const postbackKey = postbackFormState[1];
642
const postbackReferenceId = postbackFormState[2];
643
const postbackBoundArity = postbackFormState[3];
644
if (
627
- postbackKeyJSON === keyJSON &&
645
isSignatureEqual.call(action, postbackReferenceId, postbackBoundArity)
646
) {
630
- // This was a match
631
- formStateMatchingIndex = formStateHookIndex;
632
- // Reuse the state that was submitted by the form.
633
- state = postbackFormState[0];
647
+ nextPostbackStateKey = createPostbackFormStateKey(
648
+ permalink,
649
+ componentKeyPath,
650
+ formStateHookIndex,
651
+ );
652
+ if (postbackKey === nextPostbackStateKey) {
653
+ // This was a match
654
+ formStateMatchingIndex = formStateHookIndex;
655
+ // Reuse the state that was submitted by the form.
656
+ state = postbackFormState[0];
657
+ }
658
}
659
}
660
672
dispatch.$$FORM_ACTION = (prefix: string) => {
673
const metadata: ReactCustomFormAction =
674
boundAction.$$FORM_ACTION(prefix);
651
- const formData = metadata.data;
652
- if (formData) {
653
- formData.append('$ACTION_KEY', keyJSON);
654
- }
675
676
// Override the action URL
677
if (permalink !== undefined) {
678
if (__DEV__) {
679
checkAttributeStringCoercion(permalink, 'target');
680
}
661
- metadata.action = permalink + '';
681
+ permalink += '';
682
+ metadata.action = permalink;
683
+ }
684
+
685
+ const formData = metadata.data;
686
+ if (formData) {
687
+ if (nextPostbackStateKey === null) {
688
+ nextPostbackStateKey = createPostbackFormStateKey(
689
+ permalink,
690
+ componentKeyPath,
691
+ formStateHookIndex,
692
+ );
693
+ }
694
+ formData.append('$ACTION_KEY', nextPostbackStateKey);
695
}
696
return metadata;
697
};