cc2efd8771
Tests / frontend-test (push) Successful in 1m15s
Co-authored-by: gorbenko.pavel <gorbenko.pavel@tansu.kz> Reviewed-on: http://git.tnsu.kz/timur.gorodetskiy/request-portal/pulls/18
34 lines
2.3 KiB
TypeScript
34 lines
2.3 KiB
TypeScript
import { useCallback, useLayoutEffect, useRef } from 'react';
|
|
|
|
/**
|
|
* Обёртка вокруг колбэка-пропа с НЕИЗМЕННОЙ ссылкой.
|
|
*
|
|
* Зачем: колбэк, приходящий сверху пропом (`onCodeLoaded`, `onSaved` и т.п.),
|
|
* родитель почти всегда пересоздаёт на каждом рендере. Положить такой колбэк
|
|
* в зависимости useEffect нельзя — эффект начнёт перезапускаться на ровном
|
|
* месте (для форм портала это означает повторную загрузку заявки с сервера).
|
|
* Выкинуть его из зависимостей тоже нельзя — эффект зафиксирует ПЕРВУЮ версию
|
|
* колбэка и после смены пропа будет звать устаревшее замыкание.
|
|
*
|
|
* Хук снимает обе проблемы: наружу отдаётся одна и та же функция на всю жизнь
|
|
* компонента, а внутри она всегда вызывает АКТУАЛЬНУЮ версию колбэка. Такую
|
|
* обёртку можно честно указывать в зависимостях (см. react-hooks/exhaustive-deps).
|
|
*
|
|
* Аналог `useEffectEvent` из будущих версий React; ограничен колбэками без
|
|
* возвращаемого значения — именно такие у нас в пропах.
|
|
*
|
|
* ВАЖНО: вызывать её во время РЕНДЕРА нельзя — только из эффектов и
|
|
* обработчиков событий, иначе можно прочитать колбэк от прошлого рендера.
|
|
*/
|
|
export function useStableCallback<TArgs extends unknown[]>(
|
|
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), []);
|
|
}
|