背景
firebase authenticationで二要素にsms認証を登録したい。
sms認証のためのショートメッセージの送信にreCAPTCHAを使う必要がある。
reCAPTCHAは処理が完了すると失効されるので、処理が成功しても失敗でもreCAPTCHAをリセットして新しくしたい。
reCAPTCHAの流れ
- reCAPTCHAのインスタンスを作成
- reCAPTCHAのレンダリング
- ショートメッセージの送信
- リセット処理(reCAPTCHAウィジェットを削除とreCAPTCHAのインスタンスの破棄、新たなreCAPTCHAのインスタンスを作成)
発生したエラー
一度ショートメッセージの送信処理を実行し、再度ショートメッセージの送信を使用とすると以下のエラーが出る。
reCAPTCHA has already been rendered in this element
原因はreCAPTCHAのウィジェットは同じ要素に複数のウィジェットを表示することができず、再度ショートメッセージの送信した時に既存のウィジェットが残っていたため。
この時に行ってしたリセット処理。
verifierRef.current.clear(); // reCAPTCHAウィジェットを削除とreCAPTCHAのインスタンスの破棄
// 本来はclear()で済むはずだがうまくいかないので以下も追加
verifierRef.current = undefined; // reCAPTCHAのインスタンスのrefをundefinedにする
reCAPTCHARef.current.innerHTML = ''; // reCAPTCHAウィジェット要素をリセットする
verifierRef.current.clear()
でうまくいかないのでreCAPTCHAのインスタンスのrefをundefinedにしたり、reCAPTCHAウィジェット要素をリセットしたりしたが、エラーは解消できなかった。
解決した方法
コンポーネント側にdiv要素を追加し、isShowElementでレンダリングの有無を制御する。
処理が成功しても失敗でも、finallyでisShowElementをfalseにしてウィジェット要素を非表示にする。
isShowElementがfalseになるとuseEffectでtrueになり、新しいreCAPTCHAのインスタンスが生成される。
export const Test = () => {
const { reCAPTCHARef, isShowElement } = useTest();
return <div>{isShowElement && <div ref={reCAPTCHARef}></div>}</div>;
};
const reCAPTCHARef = useRef<HTMLDivElement>(null);
const verifierRef = useRef<RecaptchaVerifier>();
const [isShowElement, setIsShowElement] = useState(true);
useEffect(() => {
if (!isShowElement) {
setIsShowElement(true);
return;
}
if (!reCAPTCHARef.current) return;
const verifier = new RecaptchaVerifier(
reCAPTCHARef.current,
{
size: 'normal',
},
firebaseAuth,
);
verifierRef.current = verifier;
}, [isShowElement, reCAPTCHARef]);
const handleSendPhone = useCallback(async (phoneInfoOptions: PhoneMultiFactorEnrollInfoOptions) => {
try {
if (!verifierRef.current) throw new Error('not exist recaptchaVerifier');
// ウィジェットの表示
verifierRef.current.render();
// 指定した電話番号にショートメッセージを送信する
const phoneAuthProvider = new PhoneAuthProvider(firebaseAuth);
await phoneAuthProvider.verifyPhoneNumber(phoneInfoOptions, verifierRef.current);
} catch (error) {
console.error(error);
} finally {
setIsShowElement(false);
}
}, []);
最後に
なんでclearメソッドが使えないのか分からないが、動いてよかった。