Liveview form submission and submit hook timing, how do you manage?

I am curious to understand how people and intercepting form submission in a live_view with a Hook and making modifications to any inputs before saving the form.

The context to my question was attempting to setup Google reCaptcha v3 into the phx.gen.auth registration liveview.

After a lot of hair pulling I determined that the hook I had added to the form and the phx-submit call were being performed in parallel and because the update to the hidden input was taking longer the websocket call to my save function I wasn’t seeing it in the params to the save.

I could simulate the same behaviour with a sleep in the hook like this:

Hooks.ReCaptcha = {
  mounted() {
    this.el.addEventListener("submit", async (event) => {
      event.preventDefault();

      console.log("Intercepted submit", event);

      const sleep = (delay) => new Promise((resolve) => setTimeout(resolve, delay));
      console.log("Sleeping for 2 seconds");
      await sleep(2000);
      console.log("Done sleeping");

      const recaptchaInput = event.target.querySelector('#recaptcha_token');
      console.log("Found recaptcha input:", recaptchaInput);

      if (recaptchaInput) {
        recaptchaInput.value = "test";
      }
      this.el.submit();
    });
  }
}

Anyway, mostly curious to understand do people just not use live_view for forms, is there something I am missing. Anything like that!

You’re likely missing a event.stopPropagation(). I’d expect phoenix listens for submit event higher up in the dom.

2 Likes

This is actually documented, apparently.

1 Like

That was exactly it! I’m so mad I haven’t come across stopPropigation before :person_facepalming:

They don’t provide a complete solution in the documentation and I found the this.el.submit wasn’t working so I would be interested in any opinions on this approach with the flag and the requestSubmit method.

Hooks.ReCaptcha = {
  mounted() {
    this.el.addEventListener("submit", async (event) => {
      if (!this.shouldSubmit()) {
        // prevent the event from bubbling to the default LiveView handler
        event.stopPropagation()
        // prevent the default browser behavior (submitting the form over HTTP)
        event.preventDefault()

        console.log("Intercepted submit", event);

        const sleep = (delay) => new Promise((resolve) => setTimeout(resolve, delay));
        console.log("Sleeping for 2 seconds");
        await sleep(2000);
        console.log("Done sleeping");

        const recaptchaInput = event.target.querySelector('#recaptcha_token');
        console.log("Found recaptcha input:", recaptchaInput);

        if (recaptchaInput) {
          recaptchaInput.value = "test";
        }
        this.submit_flag = true;
        console.log("this.submit_flag", this.submit_flag);
        this.el.requestSubmit();
      } 
    });
  },
  shouldSubmit() {
    console.log("Checking if we should submit the form", this.submit_flag);
    return this.submit_flag;
  },
  submit_flag: false,
}

Form bindings — Phoenix LiveView v1.0.17 just one headline up from the other link

Cool, so that would suggest form.dispatchEvent(new Event("submit", {bubbles: true, cancelable: true})) is better than handleSubmit.

I was mostly curious from the perspective of handling the state in the Hook, but I’m going to assume it doesn’t look too terrible then.