react-teleportal
Version:
Alternative React portal implementation, giving you control over portal rendering.
82 lines (81 loc) • 2.1 kB
JavaScript
// src/portal.tsx
import React, {
createContext,
useState,
useCallback,
useMemo,
useContext,
useId,
useLayoutEffect,
useEffect
} from "react";
var useIsomorphicLayoutEffect = typeof document === "undefined" ? useEffect : useLayoutEffect;
var PortalContext = createContext(null);
var PortalProvider = ({ children }) => {
const [portalChildren, setPortalChildren] = useState(/* @__PURE__ */ new Map());
const add = useCallback(
(id, element) => {
setPortalChildren((prevChildren) => {
const nextChildren = new Map(prevChildren);
nextChildren.set(id, element);
return nextChildren;
});
},
[setPortalChildren]
);
const remove = useCallback(
(id) => {
setPortalChildren((prevChildren) => {
const nextChildren = new Map(prevChildren);
nextChildren.delete(id);
return nextChildren;
});
},
[setPortalChildren]
);
const registry = useMemo(
() => ({
add,
remove,
children: portalChildren
}),
[add, remove, portalChildren]
);
return /* @__PURE__ */ React.createElement(PortalContext.Provider, {
value: registry
}, children);
};
var usePortalRegistry = () => {
const context = useContext(PortalContext);
if (context === null) {
throw new Error(
"`usePortalRegistry` must be used within a `<PortalProvider />`"
);
}
return context;
};
var PortalOutlet = ({
children: render = (nodes) => nodes
}) => {
const registry = usePortalRegistry();
const children = Array.from(registry.children.entries()).map(
([id, node]) => node.key === null ? React.cloneElement(node, { key: id }) : node
);
return /* @__PURE__ */ React.createElement(React.Fragment, null, render(children));
};
var Portal = ({ children }) => {
const id = useId();
const registry = usePortalRegistry();
useIsomorphicLayoutEffect(() => {
registry.add(id, children);
}, [children]);
useIsomorphicLayoutEffect(() => {
return () => registry.remove(id);
}, []);
return null;
};
export {
Portal,
PortalOutlet,
PortalProvider
};