Правило было не задано вовсе: плагин eslint-plugin-react-hooks подключает
профиль @microsoft/eslint-config-spfx/lib/profiles/react, но правил из него
не включает. Теперь в .eslintrc.js явно заданы rules-of-hooks (error) и
exhaustive-deps (warn); сборка проходит с нулём предупреждений.
Что реально чинилось, а не затыкалось:
- shared/useStableCallback.ts — обёртка с неизменной ссылкой над колбэком-пропом
(аналог будущего useEffectEvent). Ссылка стабильна, внутри всегда актуальная
версия колбэка, поэтому её можно честно класть в зависимости.
- onCodeLoaded во всех forms/*/*View.tsx и *Form.tsx идёт в эффект загрузки
через эту обёртку: просто дописать проп в зависимости нельзя — заявка
перезапрашивалась бы на каждый рендер родителя.
- useRequestSubmit: run стабилизирован (useCallback + savingRef вместо чтения
состояния), возвращаемый объект мемоизирован. Формы берут из него стабильный
setError и указывают в зависимостях его, а не submit целиком.
- shared/usePersistedQuery.ts (useRoutedQuery) — было устаревшее замыкание:
эффект навсегда запоминал первую версию onQueryChange, а та замкнута на route
на момент монтирования, то есть набранный запрос мог уехать в маршрут чужого
раздела. Теперь зовётся актуальная версия.
- propertyPane/DebugUserField.tsx — то же самое с onResolved.
- Формы: в эффект "новая заявка по умолчанию на себя" дописан context
(единственный экземпляр на жизнь веб-части, лишних перезапусков не даёт),
в ReferralForm ещё и мемоизированный approval.
- RequestsTableView: dataKey в зависимости (он и так выводится из view/
statusFilter/debouncedQuery, которые там уже есть).
Осознанные отступления оставлены с объяснением ПОЧЕМУ: useMatrixEditor (момент
перечитывания матрицы задаёт вызывающий через reloadKey), companiesFieldValue
в RecruitingHire/RecruitingSelection.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>