This package allows you to define where you want components to end up in the react component hierarchy. It is like a managed react portal. This is useful if you are dealing with contexts or different React renderers like react-three-fiber or react-art. The code is quite simple, so instead of installing this package from npm you can just copy the code from index.tsx and use it in your project.
npm i @ricsam/react-tunnel
import { createTunnel, InTunnel, OutTunnel } from "@ricsam/react-tunnel";
// Create a tunnel instance
const tunnel = createTunnel();
// In your layout component
function Layout() {
return (
<div>
<h1>Header</h1>
<OutTunnel tunnel={tunnel} /> {/* Content will appear here */}
<div>Other content</div>
</div>
);
}
// Deep in your component tree
function DeepComponent() {
return (
<div>
<h2>Deep component</h2>
<InTunnel tunnel={tunnel}>
<div>This content will appear after Header</div>
</InTunnel>
</div>
);
}
const modalTunnel = createTunnel();
function App() {
return (
<div>
<OutTunnel tunnel={modalTunnel} />
<PageContent />
</div>
);
}
function Modal({ children, isOpen }) {
if (!isOpen) return null;
return (
<InTunnel tunnel={modalTunnel}>
<div className="modal">{children}</div>
</InTunnel>
);
}
const primaryTunnel = createTunnel();
const secondaryTunnel = createTunnel();
function Layout() {
return (
<div>
<nav>
<OutTunnel tunnel={primaryTunnel} />
</nav>
<aside>
<OutTunnel tunnel={secondaryTunnel} />
</aside>
</div>
);
}
function Feature() {
return (
<>
<InTunnel tunnel={primaryTunnel}>
<PrimaryNavContent />
</InTunnel>
<InTunnel tunnel={secondaryTunnel}>
<SidebarContent />
</InTunnel>
</>
);
}
const TunnelContext = createContext<Tunnel>(createTunnel());
function useTunnel() {
return useContext(TunnelContext);
}
// Use tunnels in your app
function App() {
const tunnel = useMemo(createTunnel, []);
return (
<TunnelContext.Provider value={tunnel}>
<OutTunnel tunnel={useTunnel().modal} />
<Content />
</TunnelContext.Provider>
);
}
// Consume tunnel anywhere
function Content() {
const tunnel = useTunnel();
return (
<InTunnel tunnel={tunnel}>
<div>This appears at OutTunnel</div>
</InTunnel>
);
}
Hook to detect if any InTunnel is currently rendering content. Useful for conditionally rendering OutTunnel wrappers:
function App() {
const open = useTunnelIsOpen(tunnel);
return (
<>
{open && (
<SidebarWrapper>
<OutTunnel tunnel={tunnel} />
</SidebarWrapper>
)}
<MySidebar />
</>
);
}
function MySidebar({ children }) {
return <InTunnel tunnel={tunnel}>{children}</InTunnel>;
}
Creates a new tunnel instance.
Sends children to the corresponding OutTunnel. Only one InTunnel is allowed per tunnel.
Renders content from the corresponding InTunnel. Multiple OutTunnels can share the same tunnel.
- Only one InTunnel is allowed per tunnel instance
- Content is cleared when InTunnel unmounts
- OutTunnel must be mounted before InTunnel for content to appear