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.