From 3f7320fb4d6910c6d5f452a62699656ec9d3549c Mon Sep 17 00:00:00 2001 From: c0mrade Date: Tue, 26 May 2026 15:37:59 +0300 Subject: [PATCH] fix(reset-password): cancel post-success redirect timer on unmount MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit handleSubmit schedules a navigate('/login') via setTimeout 2 s after a successful reset to show the success state first. If the user navigated away before the timer fired (back button, manual URL change), the callback still ran setState/navigate on an unmounted component — React warns, and the redirect could yank a user who deliberately left. Park the timer in a ref and clear it from a mount-time useEffect's cleanup. Functionally invisible on the happy path; quiet on the edge. --- src/pages/ResetPassword.tsx | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/pages/ResetPassword.tsx b/src/pages/ResetPassword.tsx index f518075..97f1a05 100644 --- a/src/pages/ResetPassword.tsx +++ b/src/pages/ResetPassword.tsx @@ -1,4 +1,4 @@ -import { useState } from 'react'; +import { useEffect, useRef, useState } from 'react'; import { useSearchParams, Link, useNavigate } from 'react-router'; import { useTranslation } from 'react-i18next'; import { authApi } from '../api/auth'; @@ -14,6 +14,15 @@ export default function ResetPassword() { const [confirmPassword, setConfirmPassword] = useState(''); const [status, setStatus] = useState<'form' | 'loading' | 'success' | 'error'>('form'); const [error, setError] = useState(''); + // Track the post-success redirect timer so unmount cancels it instead of + // firing navigate() on a torn-down component. + const redirectTimerRef = useRef>(undefined); + + useEffect(() => { + return () => { + if (redirectTimerRef.current) clearTimeout(redirectTimerRef.current); + }; + }, []); const handleSubmit = async (e: React.SyntheticEvent) => { e.preventDefault(); @@ -39,7 +48,7 @@ export default function ResetPassword() { try { await authApi.resetPassword(token, password); setStatus('success'); - setTimeout(() => navigate('/login', { replace: true }), 2000); + redirectTimerRef.current = setTimeout(() => navigate('/login', { replace: true }), 2000); } catch (err: unknown) { setStatus('error'); const error = err as { response?: { data?: { detail?: string } } };