Last active
May 10, 2023 02:13
-
-
Save Martini024/8c76e76fbd1687aac0e5498a6ce85bff to your computer and use it in GitHub Desktop.
react-router style portal management mini library
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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