Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bug: Can't submit form programatically when using server actions #27898

Closed
yousefelgoharyx opened this issue Jan 8, 2024 · 7 comments
Closed
Labels
Status: Unconfirmed A potential issue that we haven't yet confirmed as a bug

Comments

@yousefelgoharyx
Copy link

I'm using Server Actions and I'm trying to do some stuff before submitting the form so I added the following handler that does some stuff then submits the form

export const handleFormWithRecaptcha = (action: string) => {
    return (e: React.FormEvent<HTMLFormElement>) => {
        e.preventDefault();
        // Some stuff
        e.target.submit()
    }
}

But the code above throws the following error

Uncaught Error: A React form was unexpectedly submitted. If you called form.submit() manually, consider using form.requestSubmit() instead. If you're trying to use event.stopPropagation() in a submit event handler, consider also calling event.preventDefault().
    at <anonymous>:1:7

Following the error suggestion actually results in an infinite loop since I'm requesting a submit within the onSubmit handler itself. I think there should be better error messages
anyway, shouldn't I be able to just call form.submit?

React version: Whatever version Next 14.0.4 is using

Steps To Reproduce

  1. Create a form with a server action
  2. Add an obSubmit handler that does some stuff then submits the form programtically
@yousefelgoharyx yousefelgoharyx added the Status: Unconfirmed A potential issue that we haven't yet confirmed as a bug label Jan 8, 2024
@Hudsxn
Copy link

Hudsxn commented Jan 29, 2024

The reason you'll get an infinite loop on this is you're subscribing to onSubmit, inside onSubmit you're calling e.preventDefault(), which stops the default behavior. then asking it to resubmit, which fires the exact event again, causing it to recursively execute into a stack overflow error.

This isn't a bug with react, more a simple error. What you may want to do is have a conditional in place that can stop the endless recursion. I don't know what checks you want to perform, but here's an example.

const isReadyToSend = (args:any)  : boolean => {
     // do something.
}

export const handleFormWithRecaptcha = (action: string) => {
    return (e: React.FormEvent<HTMLFormElement>) => {
        if (!isReadyToSend()) {
              e.preventDefault();
             // Some stuff
             e.target.submit()
        }
    }
}

This way, you can check if the submit request needs to be intercepted, and if it needs to be intercepted, resend (if that's the desired behavior).

@yousefelgoharyx
Copy link
Author

Hmm it's 2am and i'm not really concentrated but at least the suggestion react gives leads to an infinite loop, that's really bad
also there no where in the docs where they mention how to submit programatically

@Hudsxn
Copy link

Hudsxn commented Jan 29, 2024

This isn't a react issue. You also have the right call in terms of submission. You are calling an infinite loop by your design. Also, there isn't any documentation on how to submit a form because this is standard DOM functionality, you're correct by calling e.target.submit() you simply need to re-assess your onSubmit listener and stop it from running recursively. As mentioned in my prior comment, you simply need to add a conditional to break this loop.

@Eyuelb
Copy link

Eyuelb commented Feb 3, 2024

hi @yousefelgoharyx i used a Custome Form Component for this I created a custom component that accepts the validation as Form component props with the target field TFieldValues[] as type. each time the error is fulfilled it needs to check if the second validation is true

    className,
    children,
    onSubmitHandler,
    validOn,
    id 
  }: FormComponent<TFieldValues>): React.ReactNode => {
    const formId = Array.isArray(validOn) ? validOn.join(",") : validOn;

    const handleFormSubmit = (data: TFieldValues) => {
      console.log("Custom submit function");
      onSubmit(data);
      onSubmitHandler && onSubmitHandler(data);
    };
    const onError = (
      errors?: FieldErrors<TFieldValues> | undefined,
) => {
      if (errors ) {
        const isValid =
          validOn &&
          (Array.isArray(validOn)
            ? validOn.every(
                (field) =>
                  methods.getValues(field) && errors[field] === undefined
              )
            : methods.getValues(validOn) && errors[validOn] === undefined);

        if (isValid) {
          if (onSubmitHandler) {
            Array.isArray(validOn)
              ? onSubmitHandler(methods.watch())
              : onSubmitHandler(methods.getValues(validOn));
          }
        }
        else{
          console.log({ errors, isValid });

        }
      }
    };
    return (
      <form
        id={id}
        noValidate
        onSubmit={methods.handleSubmit(handleFormSubmit, onError
        )}
        className={className}
      >
        {children}
        <input type="submit" id={id} ref={submitButtonRef} hidden />
        {/* <DevTool control={methods.control} /> */}
      </form>
    );
  };

@Hudsxn
Copy link

Hudsxn commented Feb 3, 2024

Hi @Eyuelb your code hasn't formatted correctly in markdown, to make it easier to read for OP I've formatted it, and broken it into seperate parts to make it a little easier to understand. I think maybe OP doesn't understand how the DOM events work. @yousefelgoharyx I've attached a diagram on this comment explaining where you're going wrong.

import React from 'react';

// Define the props for the Form component
interface FormComponent {
  className?: string;
  children?: React.ReactNode|React.ReactNode[];
  onSubmitHandler?: (data: TFieldValues) => void;
  validOn?: string | string[];
  id?: string;
}

const methods:Record<string,CallableFunction> = {}; 

// Assuming TFieldValues and FieldErrors are defined elsewhere in your code
type TFieldValues = any;
type FieldErrors<T> = Record<string, T>;

// Define the Form component with comments
const Form = ({
  className,
  children,
  onSubmitHandler,
  validOn,
  id
}: FormComponent): React.ReactNode => {
  // Convert validOn to a string if it's an array, otherwise use it directly
  const formId = Array.isArray(validOn) ? validOn.join(",") : validOn;

  // Handle form submission
  const handleFormSubmit = (data: TFieldValues) => {
    console.log("Custom submit function");
    onSubmitHandler && onSubmitHandler(data);
  };

  // Handle form errors
  const onError = (errors?: FieldErrors<TFieldValues> | undefined) => {
    if (errors) {
      // Check validity based on validOn
      const isValid =
        validOn &&
        (Array.isArray(validOn)
          ? validOn.every(
              (field) =>
                methods.getValues(field) && errors[field] === undefined
            )
          : methods.getValues(validOn) && errors[validOn] === undefined);

      if (isValid) {
        // Call onSubmitHandler with the form data if it exists
        if (onSubmitHandler) {
          Array.isArray(validOn)
            ? onSubmitHandler(methods.watch())
            : onSubmitHandler(methods.getValues(validOn));
        }
      } else {
        console.log({ errors, isValid });
      }
    }
  };

  // Render the form with its children
  return (
    <form
      id={id}
      noValidate
      onSubmit={methods.handleSubmit(handleFormSubmit, onError)}
      className={className}
    >
      {children}
      {/* Hidden submit button to trigger form submission */}
      <input type="submit" id={id} ref={submitButtonRef} hidden />
      {/* Uncomment the line below to include the DevTool component */}
      {/* <DevTool control={methods.control} /> */}
    </form>
  );
};

export default Form;

@yousefelgoharyx - I've got a inkling you're not fully clued about how events work in DOM. None of this is an issue with React, this needs to be marked as closed. You could do the same with any UI framework, i.e VueJS, svelt, you would still have the same issue because this is down to how you're handling the event.

What you're doing in your code is:
1.Listening for the submit event
2.Preventing the default action
3.Executing some code
4.Re-submitting the submit event, causing this to go back to step 1

Screenshot from 2024-02-03 14 05 18
When instead you should be doing this:
Screenshot from 2024-02-03 14 10 50

@Eyuelb
Copy link

Eyuelb commented Feb 3, 2024

@Hudsxn Thanks Formatting the code.I don't think this Issue is on react so it needs to be closed

@yousefelgoharyx
Copy link
Author

OH MY GOD
How did I not see this
that's really embarrassing. I guess I've spent too much time submitting forms with axios in event handlers xD
Thank you all

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Status: Unconfirmed A potential issue that we haven't yet confirmed as a bug
Projects
None yet
Development

No branches or pull requests

3 participants