firebase-js-sdk
firebase-js-sdk copied to clipboard
grecaptcha keeps a reference to my div in window.___grecaptcha_cfg.clients even after RecaptchaVerifier.clear()
Operating System
macOS 14.2 (23C64)
Browser Version
Chrome 121.0.6167.160 (Official Build) (arm64)
Firebase SDK Version
10.8.0
Firebase SDK Product:
Auth
Describe your project's tooling
Next.js 14
Describe the problem
When reloading my components (because some state changed or a code change triggered hot reload) I clear the RedirectVerifier and the innerHtml of the container div. Then when I create a new RecaptchaVerifier, pass the same div as container and try to render it I get an Error: reCAPTCHA has already been rendered in this element
.
Using the chrome devtools I found out that grecaptcha has a reference to my div in window.___grecaptcha_cfg.clients
even after I use RecaptchaVerifier.clear(). So when I try to render the new RecaptchaVerifier using the same div it breaks even if it is empty and the previos ReaptchaVerifier no longer exists.
I can work around this by manually clearing that reference between rerenders or by using a new div so it's not the same reference but I think RecaptchaVerifier.clear() should take care of this.
Steps and code to reproduce issue
Steps
- Have an empty div
- Create a RecaptchaVerifier using the div created in step 1 as container
- Render the RecaptchaVerifier (RecaptchaVerifier.render())
- Clear the RecaptchaVerifier (RecaptchaVerifier.render())
- Clear the container div (containerRef.innerHtml = '')
- Repeat steps 2 to 4
My Code
Recaptcha container
export type RecaptchaProps = {
containerRef: React.Ref<HTMLDivElement>;
};
export const Recaptcha = ({ containerRef }: RecaptchaProps) => {
return <div ref={containerRef} />;
};
RecaptchaVerifier
import { getAuth, RecaptchaVerifier } from 'firebase/auth';
import { useCallback, useEffect, useState } from 'react';
export const useRecaptcha = () => {
const [containerRef, setContainerRef] = useState<HTMLDivElement | null>(null);
const [verifier, setVerifier] = useState<RecaptchaVerifier>();
const createVerifier = useCallback((containerRef: HTMLDivElement | null) => {
let verifier: RecaptchaVerifier | undefined;
if (containerRef !== null) {
console.log(containerRef);
verifier = new RecaptchaVerifier(getAuth(), containerRef, {
size: 'invisible',
});
}
return verifier;
}, []);
const clear = useCallback(
(
verifier: RecaptchaVerifier | undefined,
containerRef: HTMLDivElement | null,
) => {
verifier?.clear();
if (containerRef !== null) {
containerRef.innerHTML = '';
}
},
[],
);
useEffect(() => {
const newVerifier = createVerifier(containerRef);
newVerifier?.render().then(
() => {
setVerifier(newVerifier);
},
(error) => console.log(error),
);
return () => clear(newVerifier, containerRef);
}, [createVerifier, containerRef, setVerifier, clear]);
return { verifier, containerRef: setContainerRef };
};
Login Page
const LoginPage = () => {
const { verifier, containerRef } = useRecaptcha()
[...]
return (
<div>
[...]
<Recaptcha containerRef={containerRef}/>
</div>
);
}