In Part 1 and Part 2, we explored how the Outlet Pattern allows components to render outside their declaration point using React Portals and a shared context. This works well for a single outlet, but what happens when your layout requires multiple dynamic target areas?
Imagine a dashboard UI with:
- A left sidebar for navigation.
- A right sidebar for contextual info.
- A bottom actions area.
You want components (modals, toolbars, notifications) to render into these zones, regardless of where they’re declared. This is where Named Outlets come into play.
The Problem with Single Outlet Context
The single Outlet context approach falls short when you need:
- Multiple, independent outlet areas.
- Dynamic component-to-outlet mappings.
- Scalable architecture for complex layouts.
Introducing Named Outlets
A Named Outlet Pattern allows components to target specific areas of the layout by name, dynamically rendering into the correct placeholder.
Architecture Overview
- NamedOutletProvider — Provides a map of named outlet refs.
-
NamedOutlet — Placeholder component with a
name
prop. - useNamedOutlet(name) — Hook to retrieve a specific outlet ref.
- createPortal — Components render into the named outlet node.
Implementing Named Outlets
Step 1: Context for Named Outlets
import { createContext, useContext, useRef } from 'react'
const NamedOutletContext = createContext({})
export function useNamedOutlet(name) {
const outlets = useContext(NamedOutletContext)
return outlets[name]?.current || null
}
Step 2: NamedOutletProvider
export function NamedOutletProvider({ children }) {
const outletRefs = useRef({})
const registerOutlet = (name, ref) => {
outletRefs.current[name] = ref
}
return (
<NamedOutletContext.Provider value={outletRefs.current}>
{children}
</NamedOutletContext.Provider>
)
}
Step 3: NamedOutlet Placeholder Component
export function NamedOutlet({ name }) {
const ref = useRef(null)
const outlets = useContext(NamedOutletContext)
useEffect(() => {
outlets[name] = ref
return () => {
delete outlets[name]
}
}, [name])
return <div ref={ref} />
}
Step 4: Rendering into a Named Outlet
import { createPortal } from 'react-dom'
export function FloatingButton() {
const targetNode = useNamedOutlet('bottom-actions')
if (!targetNode) return null
return createPortal(
<button className="floating-button">Click Me</button>,
targetNode
)
}
Usage Example
<NamedOutletProvider>
<Layout>
<SidebarLeft>
<NamedOutlet name="left-sidebar" />
</SidebarLeft>
<MainContent />
<SidebarRight>
<NamedOutlet name="right-sidebar" />
</SidebarRight>
<Footer>
<NamedOutlet name="bottom-actions" />
</Footer>
</Layout>
<FloatingButton />
</NamedOutletProvider>
Benefits of Named Outlets
- Supports complex multi-zone layouts.
- Components declaratively target a named slot.
- Clean separation of layout structure and component behavior.
- Scalable as your app grows in layout complexity.
Considerations
- Ensure outlets are mounted before trying to render into them.
- You may implement a loading/fallback mechanism for optional outlets.
- Be cautious with nesting NamedOutletProviders—scoping becomes important.
Conclusion
The Named Outlet Pattern provides a scalable, flexible way to manage complex component placements across multiple layout zones in React applications. By combining React Portals with a dynamic registry of named slots, you gain fine-grained control over layout composition while keeping your component tree clean and declarative.
Top comments (0)