Skip to content

Destroy the Sortable instance when the component is disposed - #15

Open
Kebechet wants to merge 2 commits into
AlexNek:mainfrom
Kebechet:fix/dispose-sortable-instance
Open

Destroy the Sortable instance when the component is disposed#15
Kebechet wants to merge 2 commits into
AlexNek:mainfrom
Kebechet:fix/dispose-sortable-instance

Conversation

@Kebechet

@Kebechet Kebechet commented Jul 25, 2026

Copy link
Copy Markdown

Problem

SortableList never releases anything it allocates:

  • The class declares public void Dispose(), but it does not implement IDisposable and the .razor has no @implements, so the method is dead code and is never invoked.
  • The Sortable instance created in init() is stored in a function-local var and never returned or registered, so nothing can reach it to call .destroy().
  • The imported IJSObjectReference module is a local in OnAfterRenderAsync and is likewise never disposed.

Every mount/unmount cycle (a list inside a dialog or popup, a route change) leaves behind a live Sortable instance with document-level listeners plus a leaked DotNetObjectReference.

Implementation

  • JS: track instances in a module-level Map keyed by element id and add a destroy(id) export. init now destroys a pre-existing instance under the same id before creating a new one.
  • C#: keep the module in a _module field, implement IAsyncDisposable, and on disposal call destroy, dispose the module, and dispose selfReference. JSDisconnectedException is swallowed, since a torn-down circuit has nothing left to clean up.
  • SortableList.razor gains @implements IAsyncDisposable so Blazor actually calls it.

Dispose() is replaced by DisposeAsync(). Since the old method was unreachable (the type never implemented IDisposable), no working call site can exist.

Builds clean; the two remaining CS8618 warnings are pre-existing.

Summary by CodeRabbit

  • Bug Fixes
    • Improved sortable list cleanup when components are removed or reinitialized.
    • Prevented obsolete sorting instances from remaining active after reinitialization.
    • Added safer handling during asynchronous cleanup when the connection is unavailable.

SortableList never released its Sortable instance, JS module reference or DotNetObjectReference.
Copilot AI review requested due to automatic review settings July 25, 2026 17:31
@coderabbitai

coderabbitai Bot commented Jul 25, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@Kebechet, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 52 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 3d92bab1-69c8-4167-9f56-15b0f99538d3

📥 Commits

Reviewing files that changed from the base of the PR and between 0dd8f6f and f0a5d69.

📒 Files selected for processing (1)
  • BlazorSortableList.Lib/SortableList.razor.cs
📝 Walkthrough

Walkthrough

The component now implements asynchronous disposal, retains its imported JavaScript module, and coordinates cleanup with tracked JavaScript Sortable instances.

Changes

Sortable lifecycle

Layer / File(s) Summary
Component module retention and async disposal
BlazorSortableList.Lib/SortableList.razor, BlazorSortableList.Lib/SortableList.razor.cs
SortableList<T> implements IAsyncDisposable, stores the imported module, invokes JavaScript destruction during disposal, asynchronously disposes the module, and releases its object reference.
JavaScript instance tracking and destruction
BlazorSortableList.Lib/SortableList.razor.js
The module exports destroy(id), destroys an existing instance before reinitialization, and stores newly created instances in the instances map.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant SortableList
  participant JSModule
  participant Sortable
  SortableList->>JSModule: initialize Sortable instance
  JSModule->>Sortable: create and track instance
  SortableList->>JSModule: destroy tracked instance
  JSModule->>Sortable: destroy instance
  SortableList->>JSModule: dispose module
Loading

Suggested reviewers: copilot

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: destroying the Sortable instance during component disposal.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR addresses resource leaks in the SortableList Blazor component by ensuring the underlying SortableJS instance, JS module reference, and .NET interop references are properly torn down when the component is disposed.

Changes:

  • Add JS-side tracking of Sortable instances keyed by element id, with a new destroy(id) export and automatic re-init cleanup.
  • Implement component disposal via IAsyncDisposable, keeping the imported JS module in a field and invoking destroy during teardown.
  • Update the .razor to @implements IAsyncDisposable so Blazor actually calls the disposal method.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

File Description
BlazorSortableList.Lib/SortableList.razor.js Track Sortable instances in a module-level Map, add destroy(id), and register/destroy instances by id.
BlazorSortableList.Lib/SortableList.razor.cs Persist JS module reference, implement DisposeAsync teardown, and call JS destroy + dispose module/self reference.
BlazorSortableList.Lib/SortableList.razor Implement IAsyncDisposable on the component so disposal is invoked by Blazor.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.


//await JS.InvokeVoidAsync("console.log", $"***id:{Id}, group:{Group},pull: {Pull},put:{Put},sort:{Sort}, handle:{Handle}, filter:{Filter}, forceFallback:{ForceFallback}");
await module.InvokeAsync<string>(
await _module.InvokeAsync<string>(

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed - switched to InvokeVoidAsync. init has no return value, so typing the call as <string> was relying on null marshalling.

Comment on lines +75 to +88
if (_module != null)
{
try
{
await _module.InvokeVoidAsync("destroy", Id);
await _module.DisposeAsync();
}
catch (JSDisconnectedException)
{
// The circuit is already gone, so there is nothing left to clean up on the JS side.
}

_module = null;
}

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Agreed, fixed. Managed cleanup now sits in a finally so _module and selfReference are always released, with JSDisconnectedException and JSException both swallowed. Module disposal is itself in an inner finally so a failing destroy still releases the module.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@BlazorSortableList.Lib/SortableList.razor.cs`:
- Around line 73-80: Update DisposeAsync and the initialization state so cleanup
uses the ID captured when the JavaScript Sortable instance was created, rather
than the mutable Id parameter. Store that initialized ID during init and pass it
to the destroy invocation, preserving the existing disposal flow.
- Around line 73-92: Update SortableList.DisposeAsync so managed cleanup always
runs when JavaScript disposal fails: retain the special handling for
JSDisconnectedException, but move _module = null and selfReference disposal into
appropriate finally blocks that execute regardless of other exceptions. Preserve
the existing destroy and module-disposal operations.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: e9bb6579-cecd-4058-b2a2-cf19269e2136

📥 Commits

Reviewing files that changed from the base of the PR and between 6b2c5ba and 0dd8f6f.

📒 Files selected for processing (3)
  • BlazorSortableList.Lib/SortableList.razor
  • BlazorSortableList.Lib/SortableList.razor.cs
  • BlazorSortableList.Lib/SortableList.razor.js

Comment thread BlazorSortableList.Lib/SortableList.razor.cs Outdated
Comment thread BlazorSortableList.Lib/SortableList.razor.cs
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