import { Controller } from '@hotwired/stimulus'; import React, { lazy, Suspense, FunctionComponent, StrictMode } from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; import invariant from 'tiny-invariant'; type Props = Record; type Loader = () => Promise<{ default: FunctionComponent }>; const componentsRegistry = new Map>(); export function registerComponents(components: Record): void { for (const [className, loader] of Object.entries(components)) { componentsRegistry.set(className, LoadableComponent(loader)); } } // Initialize React components when their markup appears into the DOM. // // Example: //
// export class ReactController extends Controller { static values = { component: String, props: Object }; declare readonly componentValue: string; declare readonly propsValue: Props; connect(): void { this.mountComponent(this.element as HTMLElement); } disconnect(): void { unmountComponentAtNode(this.element as HTMLElement); } private mountComponent(node: HTMLElement): void { const componentName = this.componentValue; const props = this.propsValue; const Component = this.getComponent(componentName); invariant( Component, `Cannot find a React component with class "${componentName}"` ); render( , node ); } private getComponent(componentName: string): FunctionComponent | null { return componentsRegistry.get(componentName) ?? null; } } const Spinner = () =>
; function LoadableComponent(loader: Loader): FunctionComponent { const LazyComponent = lazy(loader); const Component: FunctionComponent = (props: Props) => ( }> ); return Component; }