fix(reset-password): cancel post-success redirect timer on unmount

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.
This commit is contained in:
c0mrade
2026-05-26 15:37:59 +03:00
parent 0bcd26b1a8
commit 3f7320fb4d

View File

@@ -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<ReturnType<typeof setTimeout>>(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 } } };