UNPKG

@wordpress/compose

Version:
132 lines (122 loc) 4.47 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = useMergeRefs; var _element = require("@wordpress/element"); /** * WordPress dependencies */ /* eslint-disable jsdoc/valid-types */ /** * @template T * @typedef {T extends import('react').Ref<infer R> ? R : never} TypeFromRef */ /* eslint-enable jsdoc/valid-types */ /** * @template T * @param {import('react').Ref<T>} ref * @param {T} value */ function assignRef(ref, value) { if (typeof ref === 'function') { ref(value); } else if (ref && ref.hasOwnProperty('current')) { /* eslint-disable jsdoc/no-undefined-types */ /** @type {import('react').MutableRefObject<T>} */ref.current = value; /* eslint-enable jsdoc/no-undefined-types */ } } /** * Merges refs into one ref callback. * * It also ensures that the merged ref callbacks are only called when they * change (as a result of a `useCallback` dependency update) OR when the ref * value changes, just as React does when passing a single ref callback to the * component. * * As expected, if you pass a new function on every render, the ref callback * will be called after every render. * * If you don't wish a ref callback to be called after every render, wrap it * with `useCallback( callback, dependencies )`. When a dependency changes, the * old ref callback will be called with `null` and the new ref callback will be * called with the same value. * * To make ref callbacks easier to use, you can also pass the result of * `useRefEffect`, which makes cleanup easier by allowing you to return a * cleanup function instead of handling `null`. * * It's also possible to _disable_ a ref (and its behaviour) by simply not * passing the ref. * * ```jsx * const ref = useRefEffect( ( node ) => { * node.addEventListener( ... ); * return () => { * node.removeEventListener( ... ); * }; * }, [ ...dependencies ] ); * const otherRef = useRef(); * const mergedRefs useMergeRefs( [ * enabled && ref, * otherRef, * ] ); * return <div ref={ mergedRefs } />; * ``` * * @template {import('react').Ref<any>} TRef * @param {Array<TRef>} refs The refs to be merged. * * @return {import('react').RefCallback<TypeFromRef<TRef>>} The merged ref callback. */ function useMergeRefs(refs) { const element = (0, _element.useRef)(); const isAttachedRef = (0, _element.useRef)(false); const didElementChangeRef = (0, _element.useRef)(false); /* eslint-disable jsdoc/no-undefined-types */ /** @type {import('react').MutableRefObject<TRef[]>} */ /* eslint-enable jsdoc/no-undefined-types */ const previousRefsRef = (0, _element.useRef)([]); const currentRefsRef = (0, _element.useRef)(refs); // Update on render before the ref callback is called, so the ref callback // always has access to the current refs. currentRefsRef.current = refs; // If any of the refs change, call the previous ref with `null` and the new // ref with the node, except when the element changes in the same cycle, in // which case the ref callbacks will already have been called. (0, _element.useLayoutEffect)(() => { if (didElementChangeRef.current === false && isAttachedRef.current === true) { refs.forEach((ref, index) => { const previousRef = previousRefsRef.current[index]; if (ref !== previousRef) { assignRef(previousRef, null); assignRef(ref, element.current); } }); } previousRefsRef.current = refs; }, refs); // No dependencies, must be reset after every render so ref callbacks are // correctly called after a ref change. (0, _element.useLayoutEffect)(() => { didElementChangeRef.current = false; }); // There should be no dependencies so that `callback` is only called when // the node changes. return (0, _element.useCallback)(value => { // Update the element so it can be used when calling ref callbacks on a // dependency change. assignRef(element, value); didElementChangeRef.current = true; isAttachedRef.current = value !== null; // When an element changes, the current ref callback should be called // with the new element and the previous one with `null`. const refsToAssign = value ? currentRefsRef.current : previousRefsRef.current; // Update the latest refs. for (const ref of refsToAssign) { assignRef(ref, value); } }, []); } //# sourceMappingURL=index.js.map