Modern React 18 & 19 Integration

In modern React (v18 & v19), the most robust and idiomatic pattern for multi-window docking is using React Portals (createPortal). This preserves internal component state, React Context, and event bubbling without unmounting or remounting components when panels are dragged between stacks or popped out into auxiliary windows.

Installation

Install Strelit UI Kit from npm:

npm install strelit-ui-kit

The React Portal Docking Pattern

Instead of letting the layout engine imperatively manage DOM lifecycle, we register a generic component factory that hands container DOM elements over to React. React then projects your virtual DOM components into those containers seamlessly:

import React, { useState, useRef, useLayoutEffect } from 'react';
import { createPortal } from 'react-dom';
import { StrelitLayout, LayoutConfig } from 'strelit-ui-kit';
import 'strelit-ui-kit/dist/css/strelit-base.css';
import 'strelit-ui-kit/dist/css/themes/strelit-dark-theme.css';

interface PortalMount {
  id: string;
  element: HTMLElement;
  component: React.ReactNode;
}

export function DockingWorkspace() {
  const containerRef = useRef<HTMLDivElement>(null);
  const layoutRef = useRef<StrelitLayout | null>(null);
  const [portals, setPortals] = useState<PortalMount[]>([]);

  useLayoutEffect(() => {
    if (!containerRef.current) return;

    const config: LayoutConfig = {
      content: [{
        type: 'row',
        content: [
          {
            type: 'component',
            componentName: 'portalHost',
            title: 'Telemetry Inspector',
            componentState: { key: 'telemetry' }
          },
          {
            type: 'component',
            componentName: 'portalHost',
            title: 'Event Log Stream',
            componentState: { key: 'logs' }
          }
        ]
      }]
    };

    const layout = new StrelitLayout(config, containerRef.current);

    // Register generic host component that emits container element to React
    layout.registerComponent('portalHost', (container, componentState) => {
      const el = container.getElement()[0] || container.getElement();
      const key = componentState.key;

      const component = key === 'telemetry' 
        ? <TelemetryViewer /> 
        : <LogStreamViewer />;

      setPortals(prev => [...prev, { id: key, element: el, component }]);

      container.on('destroy', () => {
        setPortals(prev => prev.filter(p => p.id !== key));
      });
    });

    layout.init();
    layoutRef.current = layout;

    const handleResize = () => layout.updateSize();
    window.addEventListener('resize', handleResize);

    return () => {
      window.removeEventListener('resize', handleResize);
      layout.destroy();
    };
  }, []);

  return (
    <div style={{ width: '100vw', height: '100vh', position: 'relative' }}>
      <div ref={containerRef} style={{ width: '100%', height: '100%' }} />
      {portals.map(p => createPortal(p.component, p.element, p.id))}
    </div>
  );
}

Advantages of the React 19 Portal Architecture

  • Preserved State: Form inputs, scroll positions, and local component hooks (useState, useReducer) are never lost during panel drag, drop, or re-ordering.
  • Context Propagation: All React Context providers (Zustand, Redux, TanStack Query, ThemeContext) remain intact across docked panes.
  • Automatic Cleanup: When a user closes a tab, Strelit triggers container.on('destroy'), unmounting the portal cleanly and avoiding memory leaks.

⚡ Looking for multi-monitor Popout Windows in React?

Check out our dedicated Working with Popouts Tutorial to learn how Strelit coordinates state across separate browser windows.