import * as React from 'react'; import { getMergedSameEventsByProps } from '../helpers/getMergedSameEventsByProps'; import { isDOMTypeElement, isForwardRefElement, isValidNotReactFragmentElement, } from '../lib/utils'; import { warnOnce } from '../lib/warnOnce'; import type { HasRootRef } from '../types'; import { useEffectDev } from './useEffectDev'; import { useExternRef } from './useExternRef'; const warn = warnOnce('usePatchChildren'); type InjectProps = React.HTMLAttributes & React.Attributes & { ref?: React.Ref; }; type ExpectedReactElement = React.ReactElement & React.DOMAttributes>; type ChildrenElement = ExpectedReactElement | React.ReactNode; /** * Хук позволяет пропатчить переданный компонент так, чтобы можно было получить ссылку на его * DOM-элемент. Также есть возможность прокинуть дополнительные параметры. * * @param children * @param injectProps * @param externRef – полезен когда нужно прокинуть `ref` элементу выше. * * 👎 Без параметра `externRef` * ```ts * const { ref } = useSomeHook(); * const [childRef, child] = usePatchChildren(children); * React.useLayoutEffect(() => { * ref.current = childRef.current; // или ref.current(childRef.current) * }, [childRef]); * ``` * * 👍 С параметром `externRef` * ```ts * const { ref } = useSomeHook(); * const [childRef, child] = usePatchChildren(children, undefined, ref); * ``` */ export const usePatchChildren = ( children?: ChildrenElement, injectProps?: InjectProps, externRef?: React.Ref, ): [React.RefObject, ChildrenElement | undefined] => { const isValidElementResult = isValidNotReactFragmentElement(children); const isDOMTypeElementResult = isValidElementResult && isDOMTypeElement, ElementType>(children); const isForwardedRefElementResult = isValidElementResult && isForwardRefElement, ElementType>(children); const shouldUseRef = isDOMTypeElementResult || isForwardedRefElementResult; const childRef = useExternRef( shouldUseRef ? children.ref : isValidElementResult ? children.props.getRootRef : undefined, externRef, ); const mergedEventsByInjectProps = getMergedSameEventsByProps( injectProps ? injectProps : {}, isValidElementResult ? children.props : {}, ); const props = shouldUseRef ? { ref: childRef, ...injectProps, ...mergedEventsByInjectProps } : isValidElementResult ? { getRootRef: childRef, ...injectProps, ...mergedEventsByInjectProps } : undefined; const patchedChildren = isValidElementResult ? React.cloneElement(children, props) : children; useEffectDev(() => { if (!childRef.current && !shouldUseRef) { warn( 'Кажется, в children передан компонент, который не поддерживает свойство getRootRef. Мы не можем получить ссылку на корневой dom-элемент этого компонента', 'error', ); } }, [isValidElementResult ? children.type : null, shouldUseRef, childRef]); return [childRef, patchedChildren]; };