@supunlakmal/hooks
Version:
A collection of reusable React hooks
67 lines • 3.04 kB
JavaScript
import { useState, useEffect, useRef, useCallback, } from 'react';
import { createPortal } from 'react-dom';
const defaultAttributes = {};
// Helper function to find or create the portal root element
const getOrCreatePortalRoot = (id, attributes = defaultAttributes) => {
if (typeof document === 'undefined') {
// Cannot create on server
return { element: null, created: false }; // Return null-like structure
}
let element = document.getElementById(id);
let elementCreated = false;
if (!element) {
element = document.createElement('div');
element.setAttribute('id', id);
Object.entries(attributes).forEach(([key, value]) => {
element.setAttribute(key, value);
});
document.body.appendChild(element);
elementCreated = true;
}
return { element, created: elementCreated };
};
/**
* Custom hook to manage a React Portal.
* Creates a div element under document.body with a specified id and attributes,
* and returns a Portal component to render children into it.
*
* @param {UsePortalOptions} [options] - Configuration options.
* @param {string} [options.id='react-portal-root'] - The id attribute for the portal container div.
* @param {Record<string, string>} [options.attributes] - Additional HTML attributes to set on the portal container div.
* @returns {FunctionComponent<{ children: ReactNode }>} A Portal component.
*/
export const usePortal = ({ id = 'react-portal-root', // Default ID
attributes = defaultAttributes, } = {}) => {
const portalElementRef = useRef(null);
const portalCreatedByHook = useRef(false);
useEffect(() => {
if (typeof document === 'undefined')
return;
const { element, created } = getOrCreatePortalRoot(id, attributes);
portalElementRef.current = element;
portalCreatedByHook.current = created;
// Cleanup function
return () => {
// Only remove the element if this hook instance created it
if (portalCreatedByHook.current && portalElementRef.current) {
portalElementRef.current.remove();
}
// Reset refs/state on unmount
portalElementRef.current = null;
portalCreatedByHook.current = false;
};
}, [id, attributes]); // Re-run if id or attributes change
const [isMounted, setIsMounted] = useState(!!portalElementRef.current);
setIsMounted(!!portalElementRef.current);
// The Portal component definition using useCallback for stability
const Portal = useCallback(({ children }) => {
// Render children into the portal element using createPortal
// Only render if mounted client-side and portal element exists
if (isMounted && portalElementRef.current) {
return createPortal(children, portalElementRef.current);
}
return null; // Render nothing if not ready
}, [isMounted, portalElementRef.current]);
return Portal;
};
//# sourceMappingURL=usePortal.js.map