Last active
November 21, 2021 18:05
-
-
Save HTD/f056f2bc9bf9872d9212452e59332605 to your computer and use it in GitHub Desktop.
Blazor spurious updates workaround
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| using System.Collections.Generic; | |
| using System.Reflection; | |
| using System.Threading.Tasks; | |
| using Microsoft.AspNetCore.Components; | |
| namespace Woof.Blazor; | |
| /// <summary> | |
| /// Use this instead of <see cref="ComponentBase"/> to prevent <see cref="EventCallback"/>s triggers from changing the component's state and resetting all parameters. | |
| /// </summary> | |
| public abstract class ProtectedComponentBase : ComponentBase { | |
| /// <summary> | |
| /// Gets or sets a value indicating that every updates should be accepted, AKA default ComponentBase behavior. | |
| /// </summary> | |
| public bool AcceptAllUpdates { get; set; } | |
| /// <summary> | |
| /// Gets or sets a value indicating that every update to the component state should be ignored, basically making the component static. | |
| /// Set this in fully manual updates scenario. It disables the data binding completely. | |
| /// </summary> | |
| public bool RejectAllUpdates { get; set; } | |
| /// <summary> | |
| /// Prevents re-rendering if protected. | |
| /// </summary> | |
| /// <returns></returns> | |
| protected override bool ShouldRender() => IsReallyChanged; | |
| /// <summary> | |
| /// Checks if the component parameters really changed since the last call ignoring <see cref="EventCallback"/> changes. | |
| /// </summary> | |
| /// <param name="parameters">The special collection of parameters to be set on binding.</param> | |
| /// <returns>Task completed when the state tracking is done.</returns> | |
| public override async Task SetParametersAsync(ParameterView parameters) { | |
| IsReallyChanged = false; | |
| Changed.Clear(); | |
| if (!parameters.GetEnumerator().MoveNext()) { // special case - no parameters, if that does't pass, initialization fails. | |
| IsReallyChanged = true; | |
| await base.SetParametersAsync(parameters); | |
| return; | |
| } | |
| if (!Properties.GetEnumerator().MoveNext()) { // another special case - first pass with parameters, necessary for proper component initialization. | |
| foreach (var parameter in parameters) | |
| Properties.Add(parameter.Name, GetType().GetProperty(parameter.Name)); | |
| IsReallyChanged = true; | |
| await base.SetParametersAsync(parameters); | |
| return; | |
| } | |
| if (RejectAllUpdates) return; | |
| if (AcceptAllUpdates) await base.SetParametersAsync(parameters); | |
| foreach (var parameter in parameters) { // actual spurious update detection, we comparing parameters Blazor claims changed with actual component properties. | |
| var property = Properties[parameter.Name]; | |
| if (property is null) continue; | |
| if (property.GetValue(this)?.Equals(parameter.Value) != true && // value differs | |
| property.PropertyType != typeof(EventCallback) && // also NOT EventCallback | |
| property.PropertyType != typeof(EventCallback<>)) { // and NOT EventCallback<> (because it's the most harmful spurious case) | |
| Changed.Add(parameter.Name, parameter.Value); | |
| IsReallyChanged = true; | |
| } | |
| } | |
| if (IsReallyChanged) await base.SetParametersAsync(ParameterView.FromDictionary(Changed)); | |
| } | |
| /// <summary> | |
| /// Notifies the component that its state has changed. When applicable, this will cause the component to be re-rendered. | |
| /// </summary> | |
| public new void StateHasChanged() { | |
| IsReallyChanged = true; | |
| base.StateHasChanged(); | |
| IsReallyChanged = false; | |
| } | |
| /// <summary> | |
| /// True if the component's state seems to really be changed. | |
| /// </summary> | |
| private bool IsReallyChanged; | |
| /// <summary> | |
| /// Component parameter properties reflection cache. | |
| /// </summary> | |
| private readonly Dictionary<string, PropertyInfo> Properties = new(); | |
| /// <summary> | |
| /// A temporary directory of the changed component state values to build a corrected <see cref="ParameterView"/>. | |
| /// </summary> | |
| private readonly Dictionary<string, object> Changed = new(); | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment