react-on-rails 16.2.0-beta.19 → 16.2.0-beta.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/ClientRenderer.js +38 -1
- package/package.json +1 -1
package/lib/ClientRenderer.js
CHANGED
|
@@ -51,11 +51,48 @@ function renderElement(el, railsContext) {
|
|
|
51
51
|
try {
|
|
52
52
|
const domNode = document.getElementById(domNodeId);
|
|
53
53
|
if (domNode) {
|
|
54
|
+
// Check if this component was already rendered by a previous call
|
|
55
|
+
// This prevents hydration errors when reactOnRailsPageLoaded() is called multiple times
|
|
56
|
+
// (e.g., for asynchronously loaded content)
|
|
57
|
+
const existing = renderedRoots.get(domNodeId);
|
|
58
|
+
if (existing) {
|
|
59
|
+
// Only skip if it's the exact same DOM node and it's still connected to the document.
|
|
60
|
+
// If the node was replaced (e.g., via innerHTML or Turbo), we need to unmount the old
|
|
61
|
+
// root and re-render to the new node to prevent memory leaks and ensure rendering works.
|
|
62
|
+
const sameNode = existing.domNode === domNode && existing.domNode.isConnected;
|
|
63
|
+
if (sameNode) {
|
|
64
|
+
if (trace) {
|
|
65
|
+
console.log(`Skipping already rendered component: ${name} (dom id: ${domNodeId})`);
|
|
66
|
+
}
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
// DOM node was replaced (e.g., via async HTML injection) - clean up the old root
|
|
70
|
+
try {
|
|
71
|
+
if (supportsRootApi &&
|
|
72
|
+
existing.root &&
|
|
73
|
+
typeof existing.root === 'object' &&
|
|
74
|
+
'unmount' in existing.root) {
|
|
75
|
+
existing.root.unmount();
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
unmountComponentAtNode(existing.domNode);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
catch (unmountError) {
|
|
82
|
+
// Ignore unmount errors for replaced nodes
|
|
83
|
+
if (trace) {
|
|
84
|
+
console.log(`Error unmounting replaced component: ${name}`, unmountError);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
renderedRoots.delete(domNodeId);
|
|
88
|
+
}
|
|
54
89
|
const componentObj = ComponentRegistry.get(name);
|
|
55
90
|
if (delegateToRenderer(componentObj, props, railsContext, domNodeId, trace)) {
|
|
56
91
|
return;
|
|
57
92
|
}
|
|
58
|
-
// Hydrate if
|
|
93
|
+
// Hydrate if the DOM node has content (server-rendered HTML)
|
|
94
|
+
// Since we skip already-rendered components above, this check now correctly
|
|
95
|
+
// identifies only server-rendered content, not previously client-rendered content
|
|
59
96
|
const shouldHydrate = !!domNode.innerHTML;
|
|
60
97
|
const reactElementOrRouterResult = createReactOutput({
|
|
61
98
|
componentObj,
|