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

Problem integrating Altcha to Formik, event handling behaves wrongly #77

Open
teolaz opened this issue Nov 12, 2024 · 3 comments
Open

Comments

@teolaz
Copy link

teolaz commented Nov 12, 2024

Hi there!
I'm having an issue that I'm struggling to solve, I've lost many hours and I'd need a hand understanding if there's something that I'm missing of the whole...
I'm under GatsbyJS, and using Formik to handle a contact form in React, and I created a custom Field for Altcha that I use for form validation.
Here the code of my field(including some test console.log), code developed from your React example available in online documentation:

import { useField } from "formik";
import React, { useEffect, useRef } from "react";

import { getConfig } from "../../../utils/commonMethodsFrontend";

export default function Altcha() {
  const [field, meta, helpers] = useField("altcha");

  const { setValue } = helpers;

  const widgetRef = useRef(null);

  useEffect(() => {
    console.log("entering useEffect");
    const handleStateChange = (ev) => {
      console.log(ev);
      if (ev.detail) {
        // state can be: unverified, verifying, verified, error
        if (ev.detail.state === "verified") {
          // payload contains base64 encoded data for the server
          setValue(ev.detail.payload);
          console.log("set payload");
        } else {
          setValue("");
          console.log("set empty");
        }
      }
    };

    const current = widgetRef.current;

    if (current) {
      current.addEventListener("statechange", handleStateChange);
      console.log("added event listener");
      return () => {
        current.removeEventListener("statechange", handleStateChange);
        console.log("removed event listener");
      };
    }
  }, []);

  return (
    <div className="field-container">
      <div className="info">Accept the captcha to continue*</div>
      <altcha-widget
        ref={widgetRef}
        challengeurl={getConfig("netlify_functions_url") + "/altcha-challenge"}
      />
      {meta.error && meta.touched ? (
        <div className="error">
          <span>{meta.error}</span>
        </div>
      ) : null}
    </div>
  );
}

For what I know, I only instance an object of such component when I'm loading the form page, so I'm not passing it through any function that modifies the instance. The only doubt I have is for that const [field, meta, helpers] = useField("altcha"); that is giving me the possibility to set field value directly from within this component and not by passing from a parent component. The field value is saved on a virtual object of values that Formik keep for validation, so "altcha" is the name of the saved element to be checked in Formik.

The control lifecycle works correctly both in frontend and backend, but there's a strange behaviour that I cannot explain, I also tried to switch from 0.4.3 to new 1.1.0 to see if the problem changed but it seems to be worse than before.
Somehow the first time the React DOM loads, the event handler doesn't work. If you press on Altcha verification checkbox, no console.log is launched. Instead, if you go back and enter contact page again and verify with Altcha, you can see in debug console the event handler worked correctly.

You can check the strange behaviour here.

Do you have any idea about the possible problem here?
Thank you

@teolaz teolaz changed the title Problems integrating Altcha to Formik Problem integrating Altcha to Formik, event handling behave wrongly Nov 12, 2024
@teolaz teolaz changed the title Problem integrating Altcha to Formik, event handling behave wrongly Problem integrating Altcha to Formik, event handling behaves wrongly Nov 12, 2024
@teolaz
Copy link
Author

teolaz commented Nov 13, 2024

Just a small update, if it could help...

I've tried to append the "statechange" event handler only on "load" event like this

import { useField } from "formik";
import React, { useEffect, useRef } from "react";

import { getConfig } from "../../../utils/commonMethodsFrontend";

export default function Altcha() {
  const [field, meta, helpers] = useField("altcha");

  // const { value } = meta;
  const { setValue } = helpers;

  const widgetRef = useRef(null);

  useEffect(() => {
    console.log("entering useEffect");
    const handleStateChange = (ev) => {
      console.log(ev);
      if (ev.detail) {
        // state can be: unverified, verifying, verified, error
        if (ev.detail.state === "verified") {
          // payload contains base64 encoded data for the server
          setValue(ev.detail.payload);
          console.log("set payload");
        } else {
          setValue("");
          console.log("set empty");
        }
      }
    };

    const current = widgetRef.current;

    const handleLoad = () => {
      console.log("Widget fully loaded");
      current.addEventListener("statechange", handleStateChange);
    };

    if (current) {
      console.log("added load event listener");
      current.addEventListener("load", handleLoad);
    }

    return () => {
      if (current) {
        console.log("removing event listeners...");
        current.removeEventListener("load", handleLoad);
        current.removeEventListener("statechange", handleStateChange);
      }
    };
  }, [setValue]);

  return (
    <div className="field-container">
      <div className="info">Accept the captcha to continue*</div>
      <altcha-widget
        ref={widgetRef}
        challengeurl={getConfig("netlify_functions_url") + "/altcha-challenge"}
      />
      {meta.error && meta.touched ? (
        <div className="error">
          <span>{meta.error}</span>
        </div>
      ) : null}
    </div>
  );
}

but nothing changes, the widget launch events only when going back and forth between pages.

@ovx
Copy link
Contributor

ovx commented Nov 14, 2024

Hi, I don't see any import('altcha') in your code. When attaching event listeners, the widget must be loaded to make it work, so make sure, the altcha script is imported before calling addEventListener. Adding import('altcha') should do it.

@teolaz
Copy link
Author

teolaz commented Nov 14, 2024

Hi @ovx , yeah that was because I (wrongly) imported it in the parent component(s), I added the line in my component but nothing changed.

Then I read this post on stack overflow and made some tests.
It seems the first time you load the widget, document.readyState is still loading, infact if your print a console.log of document.readyState shows "interactive", then the second time you access to page altcha library has been already loaded and the loading is quicker, the console.log shows "complete".
I've been able to fix the code as written in post, by moving the event handler on window.load in case the document hasn't loaded when the widget is initialized...

Correct me if I'm wrong, I believe the react example you published on site works by "pure luck", as it's not a real life case where you need to wait for 3MB of webpacked stuff before the widget starts to work...

The code is this, I hope it will be helpful to integrate the event handling stuff into the example code you posted (for react at least, I haven't checked how the other examples work). I'll delete the staging environment as soon as you check and make some tries with console opened and tell me you've got everything :)

import { useField } from "formik";
import React, { useEffect, useRef } from "react";
import { getConfig } from "../../../utils/commonMethodsFrontend";

if (global.window) {
  // Need to import like this as this shouldn't be bundled by webpack and Gatsby SSR
  import("altcha");
}

export default function Altcha() {
  const [field, meta, helpers] = useField("altcha");
  const { setValue } = helpers;

  // Reference to the widget
  const widgetRef = useRef(null);

  useEffect(() => {
    console.log("entering useEffect...");
    console.log("document.readyState = " + document.readyState);

    const handleStateChange = (ev) => {
      console.log("handling state change...");
      if (ev.detail) {
        if (ev.detail.state === "verified") {
          console.log("setting payload value...");
          setValue(ev.detail.payload);
        } else {
          setValue("");
        }
      }
    };

    const addWidgetListener = () => {
      // Ensure the widget is loaded and available, then add the event listener
      if (widgetRef.current) {
        widgetRef.current.addEventListener("statechange", handleStateChange);
      }
    };

    // If the document is fully loaded, attach the listener immediately
    if (document.readyState === "complete") {
      console.log(
        "document.readyState is complete, I can add the widget listener now"
      );
      addWidgetListener();
    } else {
      // Otherwise, wait for the document to load before attaching the listener
      console.log(
        "document.readyState isn't complete, adding the listener on window.load ..."
      );
      window.addEventListener("load", addWidgetListener);
    }

    // Cleanup function to remove the event listeners
    return () => {
      if (widgetRef.current) {
        console.log("removing statechange event listener on widget");
        widgetRef.current.removeEventListener("statechange", handleStateChange);
      }
      console.log("removing load listener on window");
      window.removeEventListener("load", addWidgetListener);
    };
  }, [setValue]);

  return (
    <div className="field-container">
      <div className="info">Accept the captcha to continue*</div>
      <altcha-widget
        ref={widgetRef}
        challengeurl={`${getConfig("netlify_functions_url")}/altcha-challenge`}
      ></altcha-widget>
      {meta.error && meta.touched && (
        <div className="error">
          <span>{meta.error}</span>
        </div>
      )}
    </div>
  );
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants