Skip to content

Commit def8f94

Browse files
celadarisArgoZhang
andauthored
fix(AutoComplete): network delay causes input lag (#5555)
* update After testing live, i could still see problems, less when using OnBlurAsync but it was more noticeable in OnValueChanged. These changes address the core issue where the input text was being overwritten by stale data from older asynchronous operations. The dual approach (both C# and JavaScript checks) provides protection against race conditions. Even if one side fails, the other will catch it. * update switched SpinLock to SemaphoreSlim * re-design This should work better under bad network conditions * refactor: 增加 DropdownMenu 组件 * refactor: 更新下拉框组件 * refactor: 重构代码封装 AutoCompleteItem 组件 * chore: bump version 9.4.9-beta01 * revert: 移除更改 * revert: 重构代码 * revert: 撤销更改 * doc: 更新注释 --------- Co-authored-by: Argo Zhang <[email protected]>
1 parent 1ac25d9 commit def8f94

File tree

4 files changed

+96
-23
lines changed

4 files changed

+96
-23
lines changed

src/BootstrapBlazor/BootstrapBlazor.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<Project Sdk="Microsoft.NET.Sdk.Razor">
22

33
<PropertyGroup>
4-
<Version>9.4.8</Version>
4+
<Version>9.4.9-beta01</Version>
55
</PropertyGroup>
66

77
<ItemGroup>

src/BootstrapBlazor/Components/AutoComplete/AutoComplete.razor

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,12 @@
1616
placeholder="@PlaceHolder" disabled="@Disabled" @ref="FocusElement"/>
1717
<span class="form-select-append"><i class="@Icon"></i></span>
1818
<span class="form-select-append ac-loading"><i class="@LoadingIcon"></i></span>
19-
<ul class="dropdown-menu">
19+
<RenderTemplate ChildContent="RenderItems"></RenderTemplate>
20+
</div>
21+
22+
@code {
23+
RenderFragment RenderDropdown =>
24+
@<ul class="dropdown-menu">
2025
@foreach (var item in Rows)
2126
{
2227
<li @key="item" class="dropdown-item" @onclick="() => OnClickItem(item)">
@@ -34,5 +39,5 @@
3439
{
3540
<li class="dropdown-item">@NoDataTip</li>
3641
}
37-
</ul>
38-
</div>
42+
</ul>;
43+
}

src/BootstrapBlazor/Components/AutoComplete/AutoComplete.razor.cs

Lines changed: 27 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,9 @@ public partial class AutoComplete
8989

9090
private List<string>? _filterItems;
9191

92+
[NotNull]
93+
private AutoCompleteItems? _dropdown = default;
94+
9295
/// <summary>
9396
/// <inheritdoc/>
9497
/// </summary>
@@ -114,12 +117,21 @@ protected override void OnParametersSet()
114117
Items ??= [];
115118
}
116119

120+
private bool _render = true;
121+
122+
/// <summary>
123+
/// <inheritdoc/>
124+
/// </summary>
125+
/// <returns></returns>
126+
protected override bool ShouldRender() => _render;
127+
117128
/// <summary>
118129
/// Callback method when a candidate item is clicked
119130
/// </summary>
120131
private async Task OnClickItem(string val)
121132
{
122133
CurrentValue = val;
134+
123135
if (OnSelectedItemChanged != null)
124136
{
125137
await OnSelectedItemChanged(val);
@@ -135,9 +147,6 @@ private async Task OnClickItem(string val)
135147
[JSInvokable]
136148
public override async Task TriggerFilter(string val)
137149
{
138-
// Store the current input value to prevent it from being overwritten
139-
var currentInputValue = val;
140-
141150
if (OnCustomFilter != null)
142151
{
143152
var items = await OnCustomFilter(val);
@@ -161,14 +170,7 @@ public override async Task TriggerFilter(string val)
161170
_filterItems = [.. _filterItems.Take(DisplayCount.Value)];
162171
}
163172

164-
// Use currentInputValue here instead of potentially stale val
165-
CurrentValue = currentInputValue;
166-
167-
// Only trigger StateHasChanged if no binding is present
168-
if (!ValueChanged.HasDelegate)
169-
{
170-
StateHasChanged();
171-
}
173+
await TriggerChange(val);
172174
}
173175

174176
/// <summary>
@@ -178,16 +180,22 @@ public override async Task TriggerFilter(string val)
178180
[JSInvokable]
179181
public override Task TriggerChange(string val)
180182
{
181-
// Only update CurrentValue if the value has actually changed
182-
// This prevents overwriting the user's input
183-
if (CurrentValue != val)
183+
_render = false;
184+
CurrentValue = val;
185+
if (!ValueChanged.HasDelegate)
184186
{
185-
CurrentValue = val;
186-
if (!ValueChanged.HasDelegate)
187-
{
188-
StateHasChanged();
189-
}
187+
StateHasChanged();
190188
}
189+
_render = true;
190+
_dropdown.RenderContent();
191191
return Task.CompletedTask;
192192
}
193+
194+
private RenderFragment RenderItems => builder =>
195+
{
196+
builder.OpenComponent<AutoCompleteItems>(0);
197+
builder.AddAttribute(10, "ChildContent", RenderDropdown);
198+
builder.AddComponentReferenceCapture(20, dropdown => _dropdown = (AutoCompleteItems)dropdown);
199+
builder.CloseComponent();
200+
};
193201
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the Apache 2.0 License
3+
// See the LICENSE file in the project root for more information.
4+
// Maintainer: Argo Zhang([email protected]) Website: https://www.blazor.zone
5+
6+
using Microsoft.AspNetCore.Components.Rendering;
7+
8+
namespace BootstrapBlazor.Components;
9+
10+
/// <summary>
11+
/// AutoCompleteItems component
12+
/// </summary>
13+
class AutoCompleteItems : IComponent
14+
{
15+
/// <summary>
16+
/// Gets or sets the child content
17+
/// </summary>
18+
[Parameter, NotNull]
19+
public RenderFragment? ChildContent { get; set; }
20+
21+
private RenderHandle _renderHandle;
22+
23+
/// <summary>
24+
/// <inheritdoc/>
25+
/// </summary>
26+
/// <param name="renderHandle"></param>
27+
public void Attach(RenderHandle renderHandle)
28+
{
29+
_renderHandle = renderHandle;
30+
}
31+
32+
/// <summary>
33+
/// <inheritdoc/>
34+
/// </summary>
35+
/// <param name="parameters"></param>
36+
/// <returns></returns>
37+
public Task SetParametersAsync(ParameterView parameters)
38+
{
39+
parameters.SetParameterProperties(this);
40+
41+
RenderContent();
42+
return Task.CompletedTask;
43+
}
44+
/// <summary>
45+
/// Render method
46+
/// </summary>
47+
public void RenderContent()
48+
{
49+
_renderHandle.Render(BuildRenderTree);
50+
}
51+
52+
/// <summary>
53+
/// <inheritdoc/>
54+
/// </summary>
55+
/// <param name="builder"></param>
56+
private void BuildRenderTree(RenderTreeBuilder builder)
57+
{
58+
builder.AddContent(0, ChildContent);
59+
}
60+
}

0 commit comments

Comments
 (0)