JSFiddle - React, Tailwind, and code Playground
HTML
<h1>
Recaptcha v2 throws an error when it is reset and its container element is removed from DOM
</h1>
<h2>
Reproduction scenario
</h2>
<ol>
<li>Wait for recaptcha to load</li>
<li>Press the "Reset and remove recaptcha" button</li>
<li>Wait for ~40 seconds</li>
<li>Look at the browser console</li>
</ol>
<h2>
Expected results
</h2>
<ul>
<li>There are no errors</li>
</ul>
<h2>
Actual result
</h2>
<ul>
<li>
<p>
There is a browser error in the console:
</p>
<blockquote>
<code>Uncaught (in promise) Timeout</code>
</blockquote>
<p>
<strong>Stacktrace</strong> (the actual stacktrace might vary since it happens in heavily minified code)
</p>
<pre>setTimeout (async)
J @ recaptcha__en.js:100
(anonymous) @ recaptcha__en.js:285
tb @ recaptcha__en.js:284
mj @ recaptcha__en.js:456
(anonymous) @ recaptcha__en.js:458
</pre>
</li>
</ul>
<section id="recaptcha-holder">
loading recaptcha...
</section>
<button disabled id="remove-captcha">Reset and remove recaptcha</button>
<section id="time-counter-section" style="display: none">
<p>
Time passed since resetting and removing recaptcha element: <span id="time-counter"></span>.
</p>
<p id="error-indicator" style="display: none; color: orange">
Most likely there is already an error in the browser console.
</p>
</section>
JavaScript
window.recaptchaLoadedCallback = function() {
renderRecaptcha();
getRecaptchaRemoveButton().disabled = false;
}
let recaptchaWidgetId = null;
addRecaptchaScript();
getRecaptchaRemoveButton().addEventListener('click', () => {
const holder = getRecaptchaHolder();
grecaptcha.reset(recaptchaWidgetId);
holder.parentElement.removeChild(holder);
startTimeCounter();
});
function startTimeCounter() {
function getTimeCounterElement() {
return document.getElementById('time-counter');
}
function getTimeCounterContainer() {
return document.getElementById('time-counter-section');
}
function getErrorIndicatorElement() {
return document.getElementById('error-indicator');
}
getTimeCounterContainer().style.display = '';
const timeStart = Date.now();
function updateCounter() {
const secondsPassed = Math.floor((Date.now() - timeStart) / 1000);
if (secondsPassed > 40) {
getErrorIndicatorElement().style.display = '';
}
if (secondsPassed > 60) {
getTimeCounterElement().textContent = `over 1 minute`;
} else {
getTimeCounterElement().textContent = `${secondsPassed} sec`;
}
return secondsPassed;
}
updateCounter();
const intervalId = setInterval(() => {
const secondsPassed = updateCounter();
if (secondsPassed > 60) {
clearInterval(intervalId);
}
},
1000);
}
function addRecaptchaScript() {
const script = document.createElement('script');
script.innerHTML = '';
script.src = `https://www.google.com/recaptcha/api.js?render=explicit&onload=recaptchaLoadedCallback`;
script.async = true;
script.defer = true;
document.head.appendChild(script);
}
function getRecaptchaHolder() {
return document.getElementById('recaptcha-holder');
}
function getRecaptchaRemoveButton() {
return document.getElementById('remove-captcha');
}
function renderRecaptcha() {
const recaptchaHolder = getRecaptchaHolder();
recaptchaWidgetId =...