Imagine a simple React + Firebase app. There is a register handling function that should redirect the user to email verification page after register, looks like this:
const submitForm = async (e) => {
e.preventDefault();
const success = await apiCall(email, password, context);
scrollTo(0, 0);
navigate(routeEmailVerification, { replace: true });
console.log("navigate called");
};
The apiCall in this case is was something like this:
export const apiRegister = async (email, password, context, onResult) => {
const auth = getAuth();
try {
const userCredential = await createUserWithEmailAndPassword(
auth,
email,
password
);
const user = userCredential.user;
console.log("registered");
console.log(user);
} catch (registrationError) {
const errorCode = registrationError.code;
const errorMessage = registrationError.message;
console.log("apiRegister error", errorCode);
console.log(errorMessage);
return false;
}
return true;
};
All the console logs are just for me for now to verify that it executed. The above code works like charm, as expected - the user is registered, the apiCall returns true and navigate (const navigate = useNavigate();) redirects to a proper page - all good. Now for the fun part, I wanted to add logic sending the verification email, like this:
export const apiRegister = async (email, password, context, onResult) => {
const auth = getAuth();
try {
const userCredential = await createUserWithEmailAndPassword(
auth,
email,
password
);
const user = userCredential.user;
console.log("registered");
console.log(user);
try {
await sendEmailVerification(user);
console.log("Email verification sent");
} catch (verificationError) {
console.error("Email verification error:", verificationError);
onResult(false);
}
} catch (registrationError) {
const errorCode = registrationError.code;
const errorMessage = registrationError.message;
console.log("apiRegister error", errorCode);
console.log(errorMessage);
return false;
}
return true;
};
There is no exception raised, email verification seems to be sent, the apiCall returns true, navigate is called and seems like the url changes to a proper one - but suddenly the page doesn't refresh and I have no idea why.
I experimented a bit because I thought that maybe there is some weird behavior of sendEmailVerification, but this code has the same effect:
export const apiRegister = async (email, password, context, onResult) => {
const auth = getAuth();
try {
const userCredential = await createUserWithEmailAndPassword(
auth,
email,
password
);
const user = userCredential.user;
console.log("registered");
console.log(user);
} catch (registrationError) {
const errorCode = registrationError.code;
const errorMessage = registrationError.message;
console.log("apiRegister error", errorCode);
console.log(errorMessage);
return false;
}
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
await sleep(3000); // sleep for 3 seconds
console.log("sleep done");
return true;
};
Also no exception, sleep finishes, url changes, but the page is not refreshed. I guess I don't understand some React useNavigate behavior - would highly appreciate an explanation on that.
I can see similar problem being asked here - useNavigate doesn't work after an API call. I made a workaround similar to proposed:
const [redirect, setRedirect] = useState("");
useEffect(() => {
scrollTo(0, 0);
navigate(redirect, { replace: true });
}, [redirect]);
const submitForm = async (e) => {
e.preventDefault();
const success = await apiCall(email, password, context);
setRedirect(routeEmailVerification);
console.log("navigate called");
};
Then it works, but I don't understand why, so I would highly appreciate an explanation on that. Thank you!