Skip to content

Comments

Allow JS root components to reinitialize on circuit restart#22

Open
tomerqodo wants to merge 2 commits intocoderabbit_combined_20260121_augment_sentry_coderabbit_1_base_allow_js_root_components_to_reinitialize_on_circuit_restart_pr90from
coderabbit_combined_20260121_augment_sentry_coderabbit_1_head_allow_js_root_components_to_reinitialize_on_circuit_restart_pr90
Open

Allow JS root components to reinitialize on circuit restart#22
tomerqodo wants to merge 2 commits intocoderabbit_combined_20260121_augment_sentry_coderabbit_1_base_allow_js_root_components_to_reinitialize_on_circuit_restart_pr90from
coderabbit_combined_20260121_augment_sentry_coderabbit_1_head_allow_js_root_components_to_reinitialize_on_circuit_restart_pr90

Conversation

@tomerqodo
Copy link

@tomerqodo tomerqodo commented Jan 22, 2026

Benchmark PR from qodo-benchmark#90

Summary by CodeRabbit

  • New Features

    • Added support for renderer ID tracking, enabling JS root components to re-enable on the same renderer type with enhanced flexibility.
    • JS component initializers now execute only once per page lifecycle, preventing duplicate initialization.
  • Refactor

    • Reorganized dynamic JavaScript root component initialization for more consistent behavior.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Jan 22, 2026

Walkthrough

The pull request refactors JS root component initialization to track active renderer IDs and ensure initializers run only once per page load. It consolidates test coverage from a dedicated test class into an existing test fixture and simplifies startup configuration by making JS root component registration unconditional.

Changes

Cohort / File(s) Summary
JS Root Components Initialization
src/Components/Web.JS/src/Rendering/JSRootComponents.ts
Added rendererId parameter to enableJSRootComponents API; introduced currentRendererId and hasInitializedJsComponents state trackers; modified logic to allow re-enabling on the same renderer while throwing on different renderers; guards initializer invocations to execute once per page lifecycle.
Interop Call Site Update
src/Components/Web.JS/src/Rendering/WebRendererInteropMethods.ts
Updated enableJSRootComponents invocation to pass rendererId as first argument.
Test Consolidation
src/Components/test/E2ETest/Tests/StatePersistanceJSRootTest.cs, src/Components/test/E2ETest/Tests/StatePersistenceTest.cs
Removed dedicated test file StatePersistanceJSRootTest.cs (−42 lines); moved test PersistentStateIsSupportedInDynamicJSRoots into StatePersistenceTest.cs with parameterization for render modes.
Startup Configuration Simplification
src/Components/test/testassets/Components.TestServer/Program.cs, src/Components/test/testassets/Components.TestServer/RazorComponentEndpointsStartup.cs
Removed per-host configuration entry for JS root component endpoint; made JS root component registration unconditional in ConfigureServices.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 A renderer's tale, with IDs to track,
Initializers run just once, never back,
Tests unified, configurations plain,
State persists through the JS root domain! 🌿✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Description check ⚠️ Warning The description is minimal and does not follow the template. It lacks a detailed summary, explanation of changes, issue reference, test coverage documentation, and the required checklist items. Expand the description to include a summary of changes under 80 characters, detailed description of the modifications, a reference to the GitHub issue using 'Fixes #' format, and confirmation of the contributor checklist items.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Allow JS root components to reinitialize on circuit restart' clearly summarizes the main change: enabling JS root components to reinitialize when circuits restart, which aligns with the code changes that add rendererId tracking and guarded initialization logic.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@src/Components/Web.JS/src/Rendering/JSRootComponents.ts`:
- Around line 119-150: The guard in enableJSRootComponents is inverted and the
parameters map isn't stored: change the renderer check to throw when a different
renderer is trying to re-enable (i.e., if manager is set and currentRendererId
!== rendererId) so same-renderer re-enables are accepted, and restore the
parameters by assigning the passed jsComponentParameters to the module-level
jsComponentParametersByIdentifier (or the correct global name used elsewhere)
immediately after setting manager/currentRendererId so
RootComponentsFunctions.add can use it.
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e4ed9aa and 7db3efe.

📒 Files selected for processing (6)
  • src/Components/Web.JS/src/Rendering/JSRootComponents.ts
  • src/Components/Web.JS/src/Rendering/WebRendererInteropMethods.ts
  • src/Components/test/E2ETest/Tests/StatePersistanceJSRootTest.cs
  • src/Components/test/E2ETest/Tests/StatePersistenceTest.cs
  • src/Components/test/testassets/Components.TestServer/Program.cs
  • src/Components/test/testassets/Components.TestServer/RazorComponentEndpointsStartup.cs
💤 Files with no reviewable changes (2)
  • src/Components/test/testassets/Components.TestServer/Program.cs
  • src/Components/test/E2ETest/Tests/StatePersistanceJSRootTest.cs
🧰 Additional context used
🧬 Code graph analysis (3)
src/Components/test/E2ETest/Tests/StatePersistenceTest.cs (1)
src/Shared/E2ETesting/WaitAssert.cs (1)
  • Click (83-87)
src/Components/Web.JS/src/Rendering/WebRendererInteropMethods.ts (1)
src/Components/Web.JS/src/Rendering/JSRootComponents.ts (1)
  • enableJSRootComponents (120-151)
src/Components/Web.JS/src/Rendering/JSRootComponents.ts (1)
src/JSInterop/Microsoft.JSInterop.JS/src/src/Microsoft.JSInterop.ts (1)
  • DotNetObject (697-718)
🔇 Additional comments (4)
src/Components/Web.JS/src/Rendering/JSRootComponents.ts (1)

12-16: State tracking additions look good.

src/Components/Web.JS/src/Rendering/WebRendererInteropMethods.ts (1)

32-35: LGTM — updated call signature matches new API.

src/Components/test/testassets/Components.TestServer/RazorComponentEndpointsStartup.cs (1)

52-55: Unconditional JS root registration looks fine.

src/Components/test/E2ETest/Tests/StatePersistenceTest.cs (1)

279-291: Nice targeted coverage for dynamic JS roots.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Comment on lines 119 to 150
// Called by the framework
export function enableJSRootComponents(
rendererId: number,
managerInstance: DotNet.DotNetObject,
jsComponentParameters: JSComponentParametersByIdentifier,
jsComponentInitializers: JSComponentIdentifiersByInitializer
): void {
if (manager) {
// This will only happen in very nonstandard cases where someone has multiple hosts.
// It's up to the developer to ensure that only one of them enables dynamic root components.
if (manager && currentRendererId === rendererId) {
// A different renderer type (e.g., Server vs WebAssembly) is trying to enable JS root components.
// This is a multi-host scenario which is not supported for dynamic root components.
throw new Error('Dynamic root components have already been enabled.');
}

// When the same renderer type re-enables (e.g., circuit restart or new circuit on same page),
// accept the new manager. The old manager's DotNetObjectReference is no longer valid anyway
// because the old circuit is gone. We don't dispose the old manager - doing so would cause
// JSDisconnectedException because the circuit that created it no longer exists.
currentRendererId = rendererId;
manager = managerInstance;
jsComponentParametersByIdentifier = jsComponentParameters;

// Call the registered initializers. This is an arbitrary subset of the JS component types that are registered
// on the .NET side - just those of them that require some JS-side initialization (e.g., to register them
// as custom elements).
for (const [initializerIdentifier, componentIdentifiers] of Object.entries(jsComponentInitializers)) {
const initializerFunc = DotNet.findJSFunction(initializerIdentifier, 0) as JSComponentInitializerCallback;
for (const componentIdentifier of componentIdentifiers) {
const parameters = jsComponentParameters[componentIdentifier];
initializerFunc(componentIdentifier, parameters);

if (!hasInitializedJsComponents) {
// Call the registered initializers. This is an arbitrary subset of the JS component types that are registered
// on the .NET side - just those of them that require some JS-side initialization (e.g., to register them
// as custom elements).
for (const [initializerIdentifier, componentIdentifiers] of Object.entries(jsComponentInitializers)) {
const initializerFunc = DotNet.findJSFunction(initializerIdentifier, 0) as JSComponentInitializerCallback;
for (const componentIdentifier of componentIdentifiers)
initializerFunc(componentIdentifier, jsComponentParameters[componentIdentifier]);
}

hasInitializedJsComponents = true;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Fix renderer guard and restore parameters assignment.
The guard currently throws on the same renderer and allows different renderers; this flips the intended behavior. Also, jsComponentParametersByIdentifier is never assigned, so RootComponentsFunctions.add can throw at runtime.

🐛 Proposed fix
-  if (manager && currentRendererId === rendererId) {
+  if (manager && currentRendererId !== rendererId) {
     // A different renderer type (e.g., Server vs WebAssembly) is trying to enable JS root components.
     // This is a multi-host scenario which is not supported for dynamic root components.
     throw new Error('Dynamic root components have already been enabled.');
   }

   // When the same renderer type re-enables (e.g., circuit restart or new circuit on same page),
   // accept the new manager. The old manager's DotNetObjectReference is no longer valid anyway
   // because the old circuit is gone. We don't dispose the old manager - doing so would cause
   // JSDisconnectedException because the circuit that created it no longer exists.
   currentRendererId = rendererId;
   manager = managerInstance;
+  jsComponentParametersByIdentifier = jsComponentParameters;

   if (!hasInitializedJsComponents) {
     // Call the registered initializers. This is an arbitrary subset of the JS component types that are registered
     // on the .NET side - just those of them that require some JS-side initialization (e.g., to register them
     // as custom elements).
     for (const [initializerIdentifier, componentIdentifiers] of Object.entries(jsComponentInitializers)) {
       const initializerFunc = DotNet.findJSFunction(initializerIdentifier, 0) as JSComponentInitializerCallback;
       for (const componentIdentifier of componentIdentifiers)
         initializerFunc(componentIdentifier, jsComponentParameters[componentIdentifier]);
     }

     hasInitializedJsComponents = true;
   }
🤖 Prompt for AI Agents
In `@src/Components/Web.JS/src/Rendering/JSRootComponents.ts` around lines 119 -
150, The guard in enableJSRootComponents is inverted and the parameters map
isn't stored: change the renderer check to throw when a different renderer is
trying to re-enable (i.e., if manager is set and currentRendererId !==
rendererId) so same-renderer re-enables are accepted, and restore the parameters
by assigning the passed jsComponentParameters to the module-level
jsComponentParametersByIdentifier (or the correct global name used elsewhere)
immediately after setting manager/currentRendererId so
RootComponentsFunctions.add can use it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants