How to call mutate only once on page visit? (For Change Email Confirmation Links)

I'm struggling to get a mutation to fire exactly once in my application. Here's the scenario: when a user decides to reset their email, a token with a random UUID is generated and stored in our database. The new email then receives a confirmation link like base-url.com/email-reset?token={token_uuid}. The user clicks the link and lands on a page that shows a modal, which varies based on whether it's loading, successful, or encounters an error. My goal is to trigger the resetEmail mutation only once, as soon as the token from the URL becomes available. However, I've noticed that the mutation often gets triggered multiple times. This is an issue because the first successful mutation deletes the token from the backend, causing any subsequent mutations to fail and display the error modal although the email change is successful. Here's a code snippet for context:
// By default, a loading modal is displayed
const resetEmail = api.emailReset.resetEmail.useMutation({
onSuccess: () => showSuccessModal(),
onError: () => showErrorModal(),
});
useEffect(() => {
if (query.token && resetEmail.isIdle) {
resetEmail.mutate({ id: query.token });
}
}, [query.token, resetEmail]);
// By default, a loading modal is displayed
const resetEmail = api.emailReset.resetEmail.useMutation({
onSuccess: () => showSuccessModal(),
onError: () => showErrorModal(),
});
useEffect(() => {
if (query.token && resetEmail.isIdle) {
resetEmail.mutate({ id: query.token });
}
}, [query.token, resetEmail]);
I've tried using useState to set a resetEmailTried flag, but that hasn't solved the problem. I suspect this is because React's state doesn't update instantly. Any help or insights would be much appreciated 🙏
4 Replies
Josh
Josh3y ago
use a react ref that is initially false and set it to true inside your use effect after you send the query and then add a check for it being false to your if statement
// By default, a loading modal is displayed
const hasSent = useRef(false)
const resetEmail = api.emailReset.resetEmail.useMutation({
onSuccess: () => showSuccessModal(),
onError: () => showErrorModal(),
});
useEffect(() => {
// also useing happy path.
if(!query.token) return
if(!resetEmail.isIdle) return
if(hasSent.current) return
resetEmail.mutate({ id: query.token });
hasSent.current = true
}, [query.token, resetEmail]);
// By default, a loading modal is displayed
const hasSent = useRef(false)
const resetEmail = api.emailReset.resetEmail.useMutation({
onSuccess: () => showSuccessModal(),
onError: () => showErrorModal(),
});
useEffect(() => {
// also useing happy path.
if(!query.token) return
if(!resetEmail.isIdle) return
if(hasSent.current) return
resetEmail.mutate({ id: query.token });
hasSent.current = true
}, [query.token, resetEmail]);
happy path is just for readability, and a pattern ive grown to love and highly reccomend since hasSent is a ref, we dont need to put it in the dep array
clemwo
clemwoOP3y ago
Doesn't work unfortunately. Here's my code:
const emailResetSent = useRef(false);

useEffect(() => {
if (query.token && resetEmail.isIdle && !emailResetSent.current) {
resetEmail.mutate({ id: query.token });
emailResetSent.current = true;
}
}, [query.token, resetEmail]);
const emailResetSent = useRef(false);

useEffect(() => {
if (query.token && resetEmail.isIdle && !emailResetSent.current) {
resetEmail.mutate({ id: query.token });
emailResetSent.current = true;
}
}, [query.token, resetEmail]);
if I add a console.log(emailResetSent.current); directly above the mutate I still can see two mutations happening and false being printed twice
Josh
Josh3y ago
that would be because your probably in react strict mode which fully mounts and unmounts your component twice during development to make sure that your hooks are properly made try running in production mode, or turn off strict mode and you should see it work
clemwo
clemwoOP3y ago
Ok, I think I caught the issue. I have a Layout which contains a sidebar that fetches and displays data. Apparently this causes my component to render twice. If I comment out the sidebar the issue disappears. Thanks for the help, I really appreciate that!

Did you find this page helpful?