Skip to content

Instantly share code, notes, and snippets.

@Martini024
Last active May 10, 2023 02:13
Show Gist options
  • Save Martini024/8c76e76fbd1687aac0e5498a6ce85bff to your computer and use it in GitHub Desktop.
Save Martini024/8c76e76fbd1687aac0e5498a6ce85bff to your computer and use it in GitHub Desktop.
react-router style portal management mini library
import { useMap, useMount } from "ahooks"
import React, { ReactNode, Suspense, createContext, useContext, useState } from "react"
import { HtmlPortalNode, InPortal, OutPortal as OutPortalImpl, createHtmlPortalNode } from "react-reverse-portal"
interface PortalContextProps {
getPortalNode: (key: any) => HtmlPortalNode | undefined
setPortalNode: (key: any, value: any) => void
}
export const PortalContext = createContext<PortalContextProps>({
getPortalNode: () => undefined,
setPortalNode: () => {}
})
type OutPortalProps<T> = {
// For every existing property inside the type of Type
// convert it to be a ?: version
[key in keyof T]: T[key]
} & { path: string }
export function OutPortal<T>({ path, ...rest }: OutPortalProps<T>) {
const { getPortalNode } = useContext(PortalContext)
const portalNode = getPortalNode(path)
if (portalNode)
return (
<Suspense fallback={<></>}>
<OutPortalImpl {...rest} node={portalNode} />
</Suspense>
)
return <></>
}
interface PortalProps {
path: string
children?: ReactNode
component?: React.ComponentType<any>
}
export function Portal({ path, children, component }: PortalProps) {
const { getPortalNode, setPortalNode } = useContext(PortalContext)
const [cachedPortalNode, setCachedPortalNode] = useState<HtmlPortalNode>()
useMount(() => {
const portalNode = getPortalNode(path)
if (portalNode) setCachedPortalNode(portalNode)
else {
const newPortalNode = createHtmlPortalNode({ attributes: { style: "height: 100%; width: 100%; border-radius: inherit; " } })
setPortalNode(path, newPortalNode)
setCachedPortalNode(newPortalNode)
}
})
if (cachedPortalNode) {
if (children) return <InPortal node={cachedPortalNode}>{children}</InPortal>
else if (component) return <InPortal node={cachedPortalNode}>{React.createElement(component)}</InPortal>
}
return <></>
}
interface PortalOutletProps {
children: ReactNode
}
export default function PortalOutlet({ children }: PortalOutletProps) {
const [_, { get: getPortalNode, set: setPortalNode }] = useMap<string, HtmlPortalNode | undefined>([])
return <PortalContext.Provider value={{ getPortalNode, setPortalNode }}>{children}</PortalContext.Provider>
}
// Demo
function ComponentA() {
return <OutPortal<ThumbnailProps> path="thumbnail" {...propsPassingToHeavyRenderingComponent} />
}
function App() {
return (
<PortalOutlet>
<BrowserRouter>
<Portal path="thumbnail" component={HeavyRenderingComponent} />
<Routes>
<Route path="/" element={ComponentA} />
</Routes>
</BrowserRouter>
</PortalOutlet>
)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment