DEV Community

Talisson
Talisson

Posted on

Part 3 - Scaling the Outlet Pattern with Multiple Named Outlets in React

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

  1. NamedOutletProvider — Provides a map of named outlet refs.
  2. NamedOutlet — Placeholder component with a name prop.
  3. useNamedOutlet(name) — Hook to retrieve a specific outlet ref.
  4. 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
}
Enter fullscreen mode Exit fullscreen mode

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>
  )
}
Enter fullscreen mode Exit fullscreen mode

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} />
}
Enter fullscreen mode Exit fullscreen mode

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
  )
}
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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)