fix(17): IN-02 useCallback handleClose, drop exhaustive-deps disables
This commit is contained in:
@@ -22,7 +22,7 @@
|
|||||||
* T-10-16: autoComplete="new-password" prevents autofill of stored credential
|
* T-10-16: autoComplete="new-password" prevents autofill of stored credential
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { useState, useEffect, useRef } from 'react';
|
import { useState, useEffect, useRef, useCallback } from 'react';
|
||||||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
import { Loader2 } from 'lucide-react';
|
import { Loader2 } from 'lucide-react';
|
||||||
import {
|
import {
|
||||||
@@ -88,6 +88,20 @@ export function CredentialSheet({
|
|||||||
const dialogRef = useRef<HTMLDivElement>(null);
|
const dialogRef = useRef<HTMLDivElement>(null);
|
||||||
const handleDialogKeyDown = useFocusTrap(dialogRef);
|
const handleDialogKeyDown = useFocusTrap(dialogRef);
|
||||||
|
|
||||||
|
// IN-02: useCallback so handleClose can be a real dependency of the Escape
|
||||||
|
// effect (no blanket exhaustive-deps disable), matching the LinkOidc/Reset
|
||||||
|
// sheets' pattern.
|
||||||
|
const handleClose = useCallback(() => {
|
||||||
|
setPassword('');
|
||||||
|
setEmail('');
|
||||||
|
setValidationError(null);
|
||||||
|
onClose();
|
||||||
|
// Return focus to trigger element (a11y)
|
||||||
|
if (triggerRef?.current) {
|
||||||
|
triggerRef.current.focus();
|
||||||
|
}
|
||||||
|
}, [onClose, triggerRef]);
|
||||||
|
|
||||||
// Escape key closes the sheet (SettingsSheet pattern)
|
// Escape key closes the sheet (SettingsSheet pattern)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isOpen) return;
|
if (!isOpen) return;
|
||||||
@@ -98,7 +112,7 @@ export function CredentialSheet({
|
|||||||
};
|
};
|
||||||
document.addEventListener('keydown', onKeyDown);
|
document.addEventListener('keydown', onKeyDown);
|
||||||
return () => document.removeEventListener('keydown', onKeyDown);
|
return () => document.removeEventListener('keydown', onKeyDown);
|
||||||
}, [isOpen]); // eslint-disable-line react-hooks/exhaustive-deps
|
}, [isOpen, handleClose]);
|
||||||
|
|
||||||
// Focus heading on open (a11y)
|
// Focus heading on open (a11y)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -107,17 +121,6 @@ export function CredentialSheet({
|
|||||||
}
|
}
|
||||||
}, [isOpen]);
|
}, [isOpen]);
|
||||||
|
|
||||||
function handleClose() {
|
|
||||||
setPassword('');
|
|
||||||
setEmail('');
|
|
||||||
setValidationError(null);
|
|
||||||
onClose();
|
|
||||||
// Return focus to trigger element (a11y)
|
|
||||||
if (triggerRef?.current) {
|
|
||||||
triggerRef.current.focus();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const credentialMutation = useMutation({
|
const credentialMutation = useMutation({
|
||||||
mutationFn: async () => {
|
mutationFn: async () => {
|
||||||
if (mode === 'self-service') {
|
if (mode === 'self-service') {
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
* Security: T-05-24 — all copy is plain-text JSX children, no dangerouslySetInnerHTML.
|
* Security: T-05-24 — all copy is plain-text JSX children, no dangerouslySetInnerHTML.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { useEffect, useRef, useState } from 'react';
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||||
import { X, Bell, AlertCircle, Loader2, LogOut } from 'lucide-react';
|
import { X, Bell, AlertCircle, Loader2, LogOut } from 'lucide-react';
|
||||||
import { useQuery, useMutation } from '@tanstack/react-query';
|
import { useQuery, useMutation } from '@tanstack/react-query';
|
||||||
import { useNavigate } from 'react-router';
|
import { useNavigate } from 'react-router';
|
||||||
@@ -606,6 +606,15 @@ function ChangePasswordSheet({ isOpen, onClose }: ChangePasswordSheetProps) {
|
|||||||
const dialogRef = useRef<HTMLDivElement>(null);
|
const dialogRef = useRef<HTMLDivElement>(null);
|
||||||
const handleDialogKeyDown = useFocusTrap(dialogRef);
|
const handleDialogKeyDown = useFocusTrap(dialogRef);
|
||||||
|
|
||||||
|
// IN-02: useCallback so handleClose is a real dependency of the Escape effect.
|
||||||
|
const handleClose = useCallback(() => {
|
||||||
|
setCurrentPassword('');
|
||||||
|
setNewPassword('');
|
||||||
|
setConfirmPassword('');
|
||||||
|
setError(null);
|
||||||
|
onClose();
|
||||||
|
}, [onClose]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isOpen) return;
|
if (!isOpen) return;
|
||||||
const onKeyDown = (e: KeyboardEvent) => {
|
const onKeyDown = (e: KeyboardEvent) => {
|
||||||
@@ -613,7 +622,7 @@ function ChangePasswordSheet({ isOpen, onClose }: ChangePasswordSheetProps) {
|
|||||||
};
|
};
|
||||||
document.addEventListener('keydown', onKeyDown);
|
document.addEventListener('keydown', onKeyDown);
|
||||||
return () => document.removeEventListener('keydown', onKeyDown);
|
return () => document.removeEventListener('keydown', onKeyDown);
|
||||||
}, [isOpen]); // eslint-disable-line react-hooks/exhaustive-deps
|
}, [isOpen, handleClose]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isOpen && headingRef.current) {
|
if (isOpen && headingRef.current) {
|
||||||
@@ -621,14 +630,6 @@ function ChangePasswordSheet({ isOpen, onClose }: ChangePasswordSheetProps) {
|
|||||||
}
|
}
|
||||||
}, [isOpen]);
|
}, [isOpen]);
|
||||||
|
|
||||||
function handleClose() {
|
|
||||||
setCurrentPassword('');
|
|
||||||
setNewPassword('');
|
|
||||||
setConfirmPassword('');
|
|
||||||
setError(null);
|
|
||||||
onClose();
|
|
||||||
}
|
|
||||||
|
|
||||||
const changeMutation = useMutation({
|
const changeMutation = useMutation({
|
||||||
mutationFn: async () => {
|
mutationFn: async () => {
|
||||||
if (newPassword !== confirmPassword) throw new Error('mismatch');
|
if (newPassword !== confirmPassword) throw new Error('mismatch');
|
||||||
|
|||||||
Reference in New Issue
Block a user