Skip to content

Commit 37a8389

Browse files
Enhance ClientRenderer with cleanup and improved startup behavior
- Added functionality to track rendered React roots for proper cleanup on page unload, preventing memory leaks. - Implemented unmounting logic for both React 16-17 and 18+ using the appropriate APIs. - Updated client startup behavior to ensure it only runs in the browser environment, improving execution timing and reliability. These changes enhance the overall performance and memory management of the application.
1 parent 019d131 commit 37a8389

File tree

2 files changed

+38
-5
lines changed

2 files changed

+38
-5
lines changed

packages/react-on-rails/src/ClientRenderer.ts

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,19 @@
11
import type { ReactElement } from 'react';
2-
import type { RegisteredComponent, RailsContext } from './types/index.ts';
2+
import type { RegisteredComponent, RailsContext, RenderReturnType } from './types/index.ts';
33
import ComponentRegistry from './ComponentRegistry.ts';
44
import StoreRegistry from './StoreRegistry.ts';
55
import createReactOutput from './createReactOutput.ts';
66
import reactHydrateOrRender from './reactHydrateOrRender.ts';
77
import { getRailsContext } from './context.ts';
88
import { isServerRenderHash } from './isServerRenderResult.ts';
9+
import { onPageUnloaded } from './pageLifecycle.ts';
10+
import { supportsRootApi, unmountComponentAtNode } from './reactApis.cts';
911

1012
const REACT_ON_RAILS_STORE_ATTRIBUTE = 'data-js-react-on-rails-store';
1113

14+
// Track all rendered roots for cleanup
15+
const renderedRoots = new Map<string, { root: RenderReturnType; domNode: Element }>();
16+
1217
function initializeStore(el: Element, railsContext: RailsContext): void {
1318
const name = el.getAttribute(REACT_ON_RAILS_STORE_ATTRIBUTE) || '';
1419
const props = el.textContent !== null ? (JSON.parse(el.textContent) as Record<string, unknown>) : {};
@@ -95,7 +100,9 @@ function renderElement(el: Element, railsContext: RailsContext): void {
95100
You returned a server side type of react-router error: ${JSON.stringify(reactElementOrRouterResult)}
96101
You should return a React.Component always for the client side entry point.`);
97102
} else {
98-
reactHydrateOrRender(domNode, reactElementOrRouterResult as ReactElement, shouldHydrate);
103+
const root = reactHydrateOrRender(domNode, reactElementOrRouterResult as ReactElement, shouldHydrate);
104+
// Track the root for cleanup
105+
renderedRoots.set(domNodeId, { root, domNode });
99106
}
100107
}
101108
} catch (e: unknown) {
@@ -162,3 +169,27 @@ export function reactOnRailsComponentLoaded(domId: string): Promise<void> {
162169
renderComponent(domId);
163170
return Promise.resolve();
164171
}
172+
173+
/**
174+
* Unmount all rendered React components and clear roots.
175+
* This should be called on page unload to prevent memory leaks.
176+
*/
177+
function unmountAllComponents(): void {
178+
renderedRoots.forEach(({ root, domNode }) => {
179+
try {
180+
if (supportsRootApi && root && typeof root === 'object' && 'unmount' in root) {
181+
// React 18+ Root API
182+
root.unmount();
183+
} else {
184+
// React 16-17 legacy API
185+
unmountComponentAtNode(domNode);
186+
}
187+
} catch (error) {
188+
console.error('Error unmounting component:', error);
189+
}
190+
});
191+
renderedRoots.clear();
192+
}
193+
194+
// Register cleanup on page unload
195+
onPageUnloaded(unmountAllComponents);

packages/react-on-rails/src/ReactOnRails.client.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -194,9 +194,11 @@ globalThis.ReactOnRails = {
194194

195195
globalThis.ReactOnRails.resetOptions();
196196

197-
setTimeout(() => {
198-
ClientStartup.clientStartup();
199-
}, 0);
197+
if (typeof window !== 'undefined') {
198+
setTimeout(() => {
199+
ClientStartup.clientStartup();
200+
}, 0);
201+
}
200202

201203
export * from './types/index.ts';
202204
export default globalThis.ReactOnRails;

0 commit comments

Comments
 (0)