Skip to content

Commit cea5dc8

Browse files
committed
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.
1 parent dbe0b7e commit cea5dc8

File tree

2 files changed

+52
-13
lines changed

2 files changed

+52
-13
lines changed

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

Lines changed: 40 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -128,16 +128,30 @@ private async Task OnClickItem(string val)
128128

129129
private List<string> Rows => _filterItems ?? [.. Items];
130130

131+
// In AutoComplete.razor.cs, add a tracking variable:
132+
private string _userCurrentInput = string.Empty;
133+
private SpinLock _spinLock = new SpinLock(false); // false for non-tracking mode
134+
131135
/// <summary>
132136
/// TriggerFilter method
133137
/// </summary>
134138
/// <param name="val"></param>
135139
[JSInvokable]
136140
public override async Task TriggerFilter(string val)
137141
{
138-
// Store the current input value to prevent it from being overwritten
139-
var currentInputValue = val;
140-
142+
// Update the current input with SpinLock protection
143+
bool lockTaken = false;
144+
try
145+
{
146+
_spinLock.Enter(ref lockTaken);
147+
_userCurrentInput = val;
148+
}
149+
finally
150+
{
151+
if (lockTaken) _spinLock.Exit();
152+
}
153+
154+
// Process filtering
141155
if (OnCustomFilter != null)
142156
{
143157
var items = await OnCustomFilter(val);
@@ -155,19 +169,34 @@ public override async Task TriggerFilter(string val)
155169
: Items.Where(s => s.StartsWith(val, comparison));
156170
_filterItems = [.. items];
157171
}
158-
172+
159173
if (DisplayCount != null)
160174
{
161175
_filterItems = [.. _filterItems.Take(DisplayCount.Value)];
162176
}
163-
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)
177+
178+
// Check if still the latest input with SpinLock protection
179+
string latestInput;
180+
lockTaken = false;
181+
try
169182
{
170-
StateHasChanged();
183+
_spinLock.Enter(ref lockTaken);
184+
latestInput = _userCurrentInput;
185+
}
186+
finally
187+
{
188+
if (lockTaken) _spinLock.Exit();
189+
}
190+
191+
// Only update CurrentValue if this is still the latest input
192+
if (latestInput == val)
193+
{
194+
CurrentValue = val;
195+
196+
if (!ValueChanged.HasDelegate)
197+
{
198+
StateHasChanged();
199+
}
171200
}
172201
}
173202

src/BootstrapBlazor/Components/AutoComplete/AutoComplete.razor.js

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,12 @@ export function init(id, invoke) {
6363
filterDuration = 200;
6464
}
6565
const filterCallback = debounce(async v => {
66-
await invoke.invokeMethodAsync('TriggerFilter', v);
67-
el.classList.remove('is-loading');
66+
// Check if the input value is still the same
67+
// If not, this is an old operation that should be ignored
68+
if (input.dataset.lastValue === v) {
69+
await invoke.invokeMethodAsync('TriggerFilter', v);
70+
el.classList.remove('is-loading');
71+
}
6872
}, filterDuration);
6973

7074
Input.composition(input, v => {
@@ -73,6 +77,12 @@ export function init(id, invoke) {
7377
}
7478

7579
el.classList.add('is-loading');
80+
81+
// Store the current input value on the element
82+
// This helps track the latest user input
83+
input.dataset.lastValue = v;
84+
85+
// Modify the filterCallback to check if the input value has changed
7686
filterCallback(v);
7787
});
7888

0 commit comments

Comments
 (0)