Skip to content

React 16+ solution with hook and custom componentΒ #180

@brandonscript

Description

@brandonscript

Hey folks, got this working in React 16 and would love to help update your docs, but not sure where to send in the PR. Here's the code I'm using (works in React 17 with TypeScript 4 strict mode).

import {
  MutableRefObject,
  forwardRef,
  memo,
  useCallback,
  useEffect,
  useRef,
  useState,
} from "react";
import { PropsWithComponent } from "lib/types";
import { WidgetInstance } from "friendly-challenge";

interface FriendlyCaptchaOptions {
  startMode: "auto" | "focus" | "none";
}

interface FriendlyCaptchaProps {
  sitekey: string | null | undefined;
  options?: FriendlyCaptchaOptions;
  onReady?: (widget: WidgetInstance) => any;
  onStarted?: (widget: WidgetInstance) => any;
  onPass?: (solution: string) => any;
  onFail?: (solution: string | null) => any;
  onError?: (error: any) => any;
}

export const useFriendlyCaptcha = (
  {
    sitekey,
    options = {
      startMode: "none",
    },
    onReady,
    onStarted,
    onPass,
    onFail,
    onError,
  }: FriendlyCaptchaProps,
  ref: React.Ref<unknown> = null
) => {
  const [widget, setWidget] = useState<WidgetInstance | null>(null);
  const [solution, setSolution] = useState<string | null>(null);
  const [error, setError] = useState<any>(null);
  const captchaRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    if (ref && captchaRef) {
      (ref as MutableRefObject<unknown>).current = captchaRef.current;
    }
  }, [captchaRef, ref]);

  useEffect(() => {
    if (captchaRef.current) {
      captchaRef.current.setAttribute("data-sitekey", sitekey || "");
      captchaRef.current.setAttribute("data-start", options.startMode || "none");
      const widget = new WidgetInstance(captchaRef.current, {
        doneCallback: (solution) => {
          // The captcha was completed
          setSolution(solution);
          if (solution?.length > 0) {
            onPass?.(solution);
          } else {
            onFail?.(solution);
          }
        },
        errorCallback: (error) => {
          // The captcha failed to load
          setError(error);
          onError?.(error);
        },
        startedCallback: () => {
          // The captcha was started
          onStarted?.(widget);
        },
        readyCallback: () => {
          // The captcha is ready
          onReady?.(widget);
        },
      });
      setWidget(widget);
    }
  }, [onError, onFail, onPass, onReady, onStarted, options.startMode, sitekey]);

  const start = useCallback(() => {
    if (widget) {
      widget.start();
    }
  }, [widget]);

  const reset = useCallback(() => {
    if (widget) {
      widget.reset();
    }
  }, [widget]);

  if (!sitekey || !sitekey?.length) {
    console.error("FriendlyCaptcha: Missing siteKey");
    setError("FriendlyCaptcha: Missing siteKey");
  }

  return {
    start,
    solution,
    error,
    reset,
    captchaRef,
  };
};

export const FriendlyCaptcha = memo(
  forwardRef(
    <P extends {} = {}>(
      props: PropsWithComponent<FriendlyCaptchaProps> & {
        componentProps?: P;
      },
      ref?: React.Ref<unknown>
    ) => {
      const { component: Component = "div", componentProps = {} as P, ...captchaProps } = props;
      const { captchaRef } = useFriendlyCaptcha(captchaProps, ref);

      return <Component ref={captchaRef} {...componentProps} />;
    }
  )
);
FriendlyCaptcha.displayName = "FriendlyCaptcha";

Recommend using useMemo if using the component (or someone builds their own), so adding an example like this one (for my React/Next.js build) to the docs would be good:

const captcha = useMemo(
  () => (
    <FriendlyCaptcha
      sitekey={process.env.NEXT_PUBLIC_FRIENDLY_CAPTCHA_SITE_KEY}
      options={{
        startMode: "none",
      }}
      onPass={() => setCaptchaSuccess(true)}
      onFail={() => setCaptchaSuccess(false)}
    />
  ),
  []
);

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions