Skip to content

MudTreeView: Add ITreeItemData and make Children IReadOnlyCollection#12090

Merged
danielchalmers merged 4 commits intoMudBlazor:devfrom
danielchalmers:official-bison
Nov 25, 2025
Merged

MudTreeView: Add ITreeItemData and make Children IReadOnlyCollection#12090
danielchalmers merged 4 commits intoMudBlazor:devfrom
danielchalmers:official-bison

Conversation

@danielchalmers
Copy link
Member

@danielchalmers danielchalmers commented Nov 9, 2025

Closes #12083 and Closes #12082

Checklist:

  • The PR is submitted to the correct branch (dev).
  • My code follows the style of this project.
  • I've added relevant tests or confirmed existing ones.

@danielchalmers danielchalmers added breaking change This change will require consumer code updates API change Modifies the public API surface labels Nov 9, 2025
@mudbot mudbot bot added the enhancement Adds a new feature or enhances existing functionality (not fixing a defect) in the main library label Nov 10, 2025
Copy link
Contributor

Copilot AI left a comment

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 introduces the ITreeItemData<T> interface to provide a contract for tree item data and changes the Children property type from List<TreeItemData<T>> to IReadOnlyCollection<ITreeItemData<T>> across the TreeView components. This enables custom implementations of tree item data while maintaining type safety and flexibility.

Key Changes:

  • Added new ITreeItemData<T> interface that TreeItemData<T> now implements
  • Changed Children property from List<TreeItemData<T>>? to IReadOnlyCollection<ITreeItemData<T>>?
  • Updated TreeView components and examples to use two-way binding (@bind-Items) instead of separate Items/ItemsChanged parameters
  • Modified related API signatures (FilterFunc, ItemTemplate, etc.) to use the interface type

Reviewed changes

Copilot reviewed 12 out of 12 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
TreeItemData.cs Introduced ITreeItemData<T> interface and updated TreeItemData<T> to implement it with IReadOnlyCollection for Children
MudTreeViewItem.razor.cs Updated type signatures to use ITreeItemData<T> instead of TreeItemData<T>
MudTreeView.razor.cs Updated FilterFunc, ItemTemplate, and Items parameters to use interface type
TreeViewTests.cs Added test to verify server data binding works correctly with Children property
TreeViewServerTest.razor Simplified to use @bind-Items instead of manual ItemsChanged callback
TreeViewServerTest2.razor Added helper method to convert between interface and concrete types
TreeViewTest8.razor Added type conversion logic in LoadServerData method
TreeViewServerExample.razor Simplified to use @bind-Items pattern in documentation example
TreeViewFilteringExample.razor Updated FilterFunc signature to use interface type
TreeViewCustomExample.razor Updated method signature to use interface type
ApiTypeHierarchy.razor.cs Updated to work with interface type, requiring ToList() conversion pattern for adding children

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

/// <summary>
/// The child items underneath this item.
/// </summary>
IReadOnlyCollection<ITreeItemData<T>>? Children { get; set; }
Copy link

Copilot AI Nov 23, 2025

Choose a reason for hiding this comment

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

The Children property is declared as IReadOnlyCollection<ITreeItemData<T>>? with a setter, which creates an API design issue. IReadOnlyCollection<T> indicates immutability of the collection, but having a setter allows replacing the entire collection reference. This is semantically inconsistent - the interface name suggests read-only behavior, but the setter allows mutation at the reference level.

Consider one of these alternatives:

  1. Use ICollection<ITreeItemData<T>>? if the collection is meant to be mutable
  2. Make Children a get-only property if it should truly be read-only
  3. Use IEnumerable<ITreeItemData<T>>? if you only need enumeration with settability
Suggested change
IReadOnlyCollection<ITreeItemData<T>>? Children { get; set; }
IReadOnlyCollection<ITreeItemData<T>>? Children { get; }

Copilot uses AI. Check for mistakes.
Copy link
Member

@ScarletKuro ScarletKuro Nov 25, 2025

Choose a reason for hiding this comment

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

The setter is necessary for Blazor’s @bind-Items="context.Children".
This is an intentional design choice. We don’t want the list to be modified using Add, because in that case we wouldn’t know the list has changed, and the DOM wouldn’t update automatically. The end user would have to trigger the update manually, which isn’t very friendly. We would need to introduce some sort of observable list, subscribe to change and call StateHasChanged, which we don’t want to do.

The idea is that if you modify Children, you create an entirely new list.

Copy link
Member

Choose a reason for hiding this comment

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

This exactly why Add doesn't work well with Blazor #12079 (comment) which is why it's always best to push item as new collection, and IReadOnlyCollection helps to avoid it. Not end user can modify the list and wonder why something doesn't work or why it behaves strangely.

/// The child items underneath this item.
/// </summary>
public virtual List<TreeItemData<T>>? Children { get; set; }
public virtual IReadOnlyCollection<ITreeItemData<T>>? Children { get; set; }
Copy link

Copilot AI Nov 23, 2025

Choose a reason for hiding this comment

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

The same API design issue exists in the implementation. The Children property is declared as IReadOnlyCollection<ITreeItemData<T>>? with a setter, which creates semantic inconsistency. While the property can be set to a new collection, the IReadOnlyCollection interface suggests the collection itself should not be modifiable, creating confusion for API consumers.

This is particularly problematic because code like item.Children.Add(...) won't compile, forcing users to replace the entire collection even for simple additions, which is inefficient and awkward.

Copilot uses AI. Check for mistakes.
Copy link
Member

Choose a reason for hiding this comment

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

Same comment like above with IReadOnlyCollection<ITreeItemData<T>>? Children { get; set; }

Comment on lines +81 to +88
var children = primaryItem.Children?.ToList() ?? new List<ITreeItemData<DocumentedType>>();
children.Add(new TreeItemData<DocumentedType>
{
Children = [],
Text = descendant.NameFriendly,
Value = descendant
});
primaryItem.Children = children;
Copy link

Copilot AI Nov 23, 2025

Choose a reason for hiding this comment

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

This code demonstrates the awkwardness created by using IReadOnlyCollection for the Children property. To add a child item, you must:

  1. Convert Children to a mutable list with .ToList()
  2. Add the new item to the list
  3. Reassign the entire collection back to Children

This pattern is inefficient and awkward. If Children were declared as ICollection<ITreeItemData<T>>? or List<ITreeItemData<T>>? instead, you could simply call primaryItem.Children.Add(...) directly, which would be more intuitive and performant.

Suggested change
var children = primaryItem.Children?.ToList() ?? new List<ITreeItemData<DocumentedType>>();
children.Add(new TreeItemData<DocumentedType>
{
Children = [],
Text = descendant.NameFriendly,
Value = descendant
});
primaryItem.Children = children;
primaryItem.Children?.Add(new TreeItemData<DocumentedType>
{
Children = [],
Text = descendant.NameFriendly,
Value = descendant
});

Copilot uses AI. Check for mistakes.
Copy link
Member

Choose a reason for hiding this comment

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

While this doesn't looks as friendly, it's quite common pattern when working with immutable list.
It doesn't mean the list will never modify and be in a "locked" state, the items might be added through creating new collection and supplying it, we just don't want the descender API to modify the collection unexpectedly.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 12 out of 12 changed files in this pull request and generated 1 comment.


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

This was referenced Feb 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

API change Modifies the public API surface breaking change This change will require consumer code updates enhancement Adds a new feature or enhances existing functionality (not fixing a defect) in the main library

Projects

None yet

Development

Successfully merging this pull request may close these issues.

MudTreeView: use ITreeItemData instead of TreeItemData Make TreeItemData.Children IReadOnlyCollection<T> instead of List<T>

3 participants