toggle-corp/react-store

View on GitHub
components/Input/ReCaptcha/index.js

Summary

Maintainability
A
2 hrs
Test Coverage
import React, { useState, useRef, useEffect, useCallback } from 'react';
import PropTypes from 'prop-types';
import { FaramInputElement } from '@togglecorp/faram';
import { _cs } from '@togglecorp/fujs';

import styles from './styles.scss';

const propTypes = {
    className: PropTypes.string,
    siteKey: PropTypes.string.isRequired,

    onloadCallback: PropTypes.func,
    onChange: PropTypes.func,
    expiredCallback: PropTypes.func,

    theme: PropTypes.string,
    type: PropTypes.string,
    size: PropTypes.string,
    tabindex: PropTypes.string,
    hl: PropTypes.string,
    badge: PropTypes.string,

    showHintAndError: PropTypes.bool,
    error: PropTypes.string,

    // eslint-disable-next-line react/forbid-prop-types
    componentRef: PropTypes.object,
};

const defaultProps = {
    className: '',
    onloadCallback: undefined,
    onChange: undefined,
    expiredCallback: undefined,
    theme: 'light',
    type: 'image',
    size: 'normal',
    tabindex: '0',
    hl: 'en',
    badge: 'bottomright',

    showHintAndError: true,
    error: '',

    componentRef: undefined,
};

const isReady = () => (window.grecaptcha && window.grecaptcha.render);

function ReCaptcha(props) {
    const {
        className,
        showHintAndError,
        error,

        expiredCallback,
        onChange,
        onloadCallback,
        siteKey,
        theme,
        size,
        tabindex,
        type,
        hl,
        badge,
        componentRef,
    } = props;

    const [ready, setReady] = useState(isReady);
    const reCaptchaDom = useRef();
    const widgetRef = useRef();

    const containerStyle = _cs(styles.recaptcha, className);
    const reCaptchaStyle = _cs(styles.gRecaptcha, showHintAndError && error && styles.errored);

    useEffect(
        () => {
            if (ready) {
                return () => {};
            }
            const timer = setInterval(
                () => {
                    const r = isReady();
                    if (r) {
                        setReady(r);
                    }
                },
                1000,
            );
            return () => {
                clearTimeout(timer);
            };
        },
        [ready],
    );

    useEffect(
        () => {
            if (!ready) {
                return;
            }

            widgetRef.current = window.grecaptcha.render(reCaptchaDom.current, {
                sitekey: siteKey,
                callback: (token) => {
                    if (onChange) {
                        onChange(token);
                    }
                },
                theme,
                type,
                size,
                tabindex,
                hl,
                badge,
                'expired-callback': () => {
                    if (onChange) {
                        onChange('');
                    }
                    if (expiredCallback) {
                        expiredCallback();
                    }
                },
            });

            if (onloadCallback) {
                onloadCallback();
            }
        },
        [
            ready,
            siteKey, theme, size, tabindex, type, hl, badge,
            onloadCallback, onChange, expiredCallback,
        ],
    );

    const handleReset = useCallback(
        () => {
            if (ready && widgetRef.current !== null) {
                window.grecaptcha.reset(widgetRef.current);
            }
            if (onChange) {
                onChange('');
            }
        },
        [onChange, ready],
    );
    useEffect(
        () => {
            componentRef.current = {
                reset: handleReset,
            };
        },
        [handleReset, componentRef],
    );

    let errorComp;
    if (showHintAndError) {
        if (error) {
            errorComp = (
                <p
                    className={styles.error}
                    key="error"
                >
                    {error}
                </p>
            );
        } else {
            errorComp = (
                <p
                    className={`${styles.empty} ${styles.error}`}
                    key="empty"
                >
                    -
                </p>
            );
        }
    }

    return (
        <div className={containerStyle}>
            <div
                ref={reCaptchaDom}
                className={reCaptchaStyle}
            >
                { !ready && 'Loading...' }
            </div>
            {errorComp}
        </div>
    );
}
ReCaptcha.propTypes = propTypes;
ReCaptcha.defaultProps = defaultProps;

export default FaramInputElement(ReCaptcha);