import { useCallback, useLayoutEffect, useRef } from 'react'; /** * Обёртка вокруг колбэка-пропа с НЕИЗМЕННОЙ ссылкой. * * Зачем: колбэк, приходящий сверху пропом (`onCodeLoaded`, `onSaved` и т.п.), * родитель почти всегда пересоздаёт на каждом рендере. Положить такой колбэк * в зависимости useEffect нельзя — эффект начнёт перезапускаться на ровном * месте (для форм портала это означает повторную загрузку заявки с сервера). * Выкинуть его из зависимостей тоже нельзя — эффект зафиксирует ПЕРВУЮ версию * колбэка и после смены пропа будет звать устаревшее замыкание. * * Хук снимает обе проблемы: наружу отдаётся одна и та же функция на всю жизнь * компонента, а внутри она всегда вызывает АКТУАЛЬНУЮ версию колбэка. Такую * обёртку можно честно указывать в зависимостях (см. react-hooks/exhaustive-deps). * * Аналог `useEffectEvent` из будущих версий React; ограничен колбэками без * возвращаемого значения — именно такие у нас в пропах. * * ВАЖНО: вызывать её во время РЕНДЕРА нельзя — только из эффектов и * обработчиков событий, иначе можно прочитать колбэк от прошлого рендера. */ export function useStableCallback( callback: ((...args: TArgs) => void) | undefined ): (...args: TArgs) => void { const ref = useRef(callback); // Обновляем ссылку до того, как отработают обычные (passive) эффекты, — // так вызов из useEffect гарантированно попадает в свежий колбэк. useLayoutEffect(() => { ref.current = callback; }); return useCallback((...args: TArgs) => ref.current?.(...args), []); }