I'm using Syncfusion's SfTextBox component in a Blazor Server application. I have bound the @bind-Value
to a property in my model and added a ValueChange
event for additional logic.
However, the ValueChange
event is not triggering, and the bound property remains null.
<SfTextBox @bind-value=@Value
Placeholder=@Placeholder
Enabled=@IsEnabled
FloatLabelType=@FloatLabelType.Never
CssClass="" />
public partial class SignInComponent : ComponentBase
{
[Inject]
public IIdentityViewService IdentityViewService { get; set; }
[Inject]
public AuthenticationStateProvider AuthStateProvider { get; set; }
public ComponentState State { get; set; }
public IdentityComponentException Exception { get; set; }
public SignInView SignInView { get; set; }
public TextBoxBase SignInEmailTextBox { get; set; }
public TextBoxBase SignInPasswordTextBox { get; set; }
public ButtonBase SubmitButton { get; set; }
public SpinnerBase Spinner { get; set; }
protected override void OnInitialized()
{
SignInView = new SignInView();
State = ComponentState.Content;
}
}
public partial class TextBoxBase : ComponentBase
{
[Parameter]
public string Value { get; set; }
[Parameter]
public string Placeholder { get; set; }
[Parameter]
public string CssClass { get; set; }
[Parameter]
public EventCallback<string> ValueChanged { get; set; }
[Parameter]
public bool IsDisabled { get; set; }
public bool IsEnabled => IsDisabled is false;
public async Task SetValue(string value)
{
this.Value = value;
await ValueChanged.InvokeAsync(this.Value);
}
private Task OnValueChanged(ChangeEventArgs changeEventArgs)
{
this.Value = changeEventArgs.Value.ToString();
//InvokeAsync(StateHasChanged);
return ValueChanged.InvokeAsync(this.Value);
}
public void Disable()
{
this.IsDisabled = true;
InvokeAsync(StateHasChanged);
}
public void Enable()
{
this.IsDisabled = false;
InvokeAsync(StateHasChanged);
}
}
<div class="py-6 flex flex-col gap-5">
<div>
<TextBoxBase
@ref=@SignInEmailTextBox
@bind-Value=@SignInView.UsernameOrEmail
Placeholder="Your email ?"
CssClass="w-full py-3 px-6 ring-1 ring-gray-300 rounded-xl placeholder-gray-600 bg-transparent transition disabled:ring-gray-200 disabled:bg-gray-100 disabled:placeholder-gray-400 invalid:ring-red-400 focus:invalid:outline-none" />
</div>
<div class="flex flex-col items-end">
<TextBoxBase
@ref=@SignInPasswordTextBox
@bind-Value=@SignInView.Password
Placeholder="What's the secret word ?"
CssClass="w-full py-3 px-6 ring-1 ring-gray-300 rounded-xl placeholder-gray-600 bg-transparent transition disabled:ring-gray-200 disabled:bg-gray-100 disabled:placeholder-gray-400 invalid:ring-red-400 focus:invalid:outline-none" />
<a href="/forgotten/password" type="reset" class="w-max p-3 -mr-3">
<span class="text-sm tracking-wide text-blue-600">Forgot password ?</span>
</a>
</div>
<div>
<ButtonBase
@ref=@SubmitButton
OnClick=@SignInAsync
Label="Login"
CssClass="w-full px-6 py-3 rounded-xl bg-sky-500 transition hover:bg-sky-600 focus:bg-sky-600 active:bg-sky-800" />
<SpinnerBase @ref=@Spinner />
</div>
</div>
You need to sort out the binding correctly i.e. set up the getter to get the provided Value
and the setter to pass the new value through by calling the ValueChanged
callback.
Here's a an example using the standard InputText
as I don't use SF. I'm pretty sure the SF TextBox will work the same. I've also shown how to implement the functionality you've shown without creating a custom control.
Note: Once you get the logic right, there no need to splatter calls to StateHasChanged
through the code.
Also see this question and answer that shows another approach which inherits directly from the Text control. - https://stackoverflow.com/a/78661731/13065781
A basic custom control with binding set up:
<InputText disabled="@IsDisabled" class="@CssClass"
placeholder="@this.Placeholder"
@bind-Value:get="@Value"
@bind-Value:set="SetValue"/>
@code {
[Parameter] public string? Value { get; set; }
[Parameter] public string? Placeholder { get; set; }
[Parameter] public string? CssClass { get; set; }
[Parameter] public EventCallback<string> ValueChanged { get; set; }
[Parameter] public bool IsDisabled { get; set; }
public bool IsEnabled => IsDisabled is false;
public async Task SetValue(string? value)
{
this.Value = value;
await ValueChanged.InvokeAsync(this.Value);
}
}
And a demo page:
@page "/"
<PageTitle>Home</PageTitle>
<h1>Hello, world!</h1>
Welcome to your new app.
<EditForm Model="_model">
<div class="mb-2">
<InputText disabled="@_isDisabled"
class="form-control"
placeholder="Enter Your Name"
@bind-Value="_model.Name" />
</div>
<div class="mb-2">
<TextBoxBase CssClass="form-control"
IsDisabled="_isDisabled"
Placeholder="Enter A Value"
@bind-Value="_model.Value" />
</div>
</EditForm>
<div class="mb-2">
<button class="btn btn-primary" @onclick="OnDisable">Disable</button>
</div>
<div class="bg-dark text-white m-1 p-1">
<pre>Name: @_model.Name</pre>
<pre>Value: @_model.Value</pre>
</div>
@code {
private Model _model = new Model();
private bool _isDisabled;
private void OnDisable()
{
_isDisabled = !_isDisabled;
}
public class Model
{
public string? Name { get; set; }
public string? Value { get; set; }
}
}
A last note. You shouldn't need to get references to edit components like this:
public TextBoxBase SignInPasswordTextBox { get; set; }
Everything should happen through Parameters.