import { createContext, Fragment, useCallback, useContext, useEffect, useId, useMemo, useRef, useState, } from 'react' type Component = React.ReactElement type ContextType = { outlet: Component | null append(id: string, component: Component): void remove(id: string): void } type ComponentMap = { [id: string]: Component | null } export function createPortalGroup() { const Context = createContext({ outlet: null, append: () => {}, remove: () => {}, }) Context.displayName = 'PortalContext' function Provider(props: React.PropsWithChildren<{}>) { const map = useRef({}) const [outlet, setOutlet] = useState(null) const append = useCallback((id, component) => { if (map.current[id]) return map.current[id] = {component} setOutlet(<>{Object.values(map.current)}) }, []) const remove = useCallback(id => { map.current[id] = null setOutlet(<>{Object.values(map.current)}) }, []) const contextValue = useMemo( () => ({ outlet, append, remove, }), [outlet, append, remove], ) return ( {props.children} ) } function Outlet() { const ctx = useContext(Context) return ctx.outlet } function Portal({children}: React.PropsWithChildren<{}>) { const {append, remove} = useContext(Context) const id = useId() useEffect(() => { append(id, children as Component) return () => remove(id) }, [id, children, append, remove]) return null } return {Provider, Outlet, Portal} } const DefaultPortal = createPortalGroup() export const Provider = DefaultPortal.Provider export const Outlet = DefaultPortal.Outlet export const Portal = DefaultPortal.Portal