2

I think I must have missed something here... I thought OnParametersSet would be called if I assign a new value to a parameter but it seems it's not the case. I'm trying to make a component re-render each time I assign it a new value to one of it's parameter.

For example, the below is page conatining "Clock" component. Every second, I update the parameter value.

@code {

    public TimeSpan Duration { get; set; }
    public Timer Updater { get; set; }

    protected override void OnInitialized()
    {
        base.OnInitialized();
        this.Duration = TimeSpan.FromMinutes(60);

        this.Updater = new System.Timers.Timer(1000);
        Updater.Elapsed += OnTimerElapsed;
        Updater.Enabled = true;
    }

    private void OnTimerElapsed(Object source, ElapsedEventArgs eventArgs)
    {
        this.Duration = this.Duration.Add(TimeSpan.FromSeconds(-1));
        Debug.WriteLine($"New Duration is {this.Duration.ToString()}");
    }
}

<Clock Value="@this.Duration" />

The below is the clock razor component.

@code {
    [Parameter]
    public TimeSpan Value { get; set; }
}

<div class="clock">
    <div class="minutes">@this.Value.Minutes</div>
    <div class="min-sec-separator">:</div>
    <div class="seconds">@this.Value.Seconds</div>
</div>

It will be really appreciated if someone could point me out what I am missing here.

Give the value is TimeSpan, I expected this should just work without any intervention.

2

3 Answers 3

2

@Buga's answer is correct, but you need to understand why.

ComponentBase implements IHandleEvent. When this interface is implemented all UI driven events call the IHandleEvent.HandleEventAsync method, passing in the handler and the event arguments. The ComponentBase implementation calls StateHaschanged when (if the handler is async and yields) and after it executes the handler.

However, OnTimerElapsed is just a normal event handler, so there's no automated calling of StateHasChanged. It's one of the few occasions you need to manually call it.

which handles all UI driven events.

3
  • Thanks for additional explanation @MrC. What's still unclear to me is what happened to "[Parameter] public TimeSpan Value {get; set;}" itself. I thought that it would detect the value being assigned to it and causes the clock component itself initiate a new rendering or at least call OnParametersSet... but none happened. Commented Dec 13, 2022 at 23:17
  • 1
    There's not automated process here. When the parent renders, the renderer checks each child's "ParameterList" to determine whether any have changed and then calls SetParametersAsync on any child where a change "may" have occured. To drive change automatically, move the timer into a service and use the Notification pattern - stackoverflow.com/a/69562295/13065781 Commented Dec 13, 2022 at 23:26
  • 1
    Here's another similar answer - stackoverflow.com/questions/74530150/… Commented Dec 13, 2022 at 23:31
1

Try(parent):

@using System.Timers

@code {

    public TimeSpan Duration { get; set; }
    public Timer Updater { get; set; }

    protected override void OnInitialized()
    {
        this.Duration = TimeSpan.FromMinutes(60);

        this.Updater = new System.Timers.Timer(1000);
        Updater.Elapsed += OnTimerElapsed;
        Updater.Enabled = true;
        base.OnInitialized();

    }

    private void OnTimerElapsed(Object source, ElapsedEventArgs eventArgs)
    {
        this.Duration = this.Duration.Add(TimeSpan.FromSeconds(-1));
        StateHasChanged();
        Console.WriteLine($"New Duration is {this.Duration.ToString()}");
    }
}

<Clock Value="@this.Duration" />

And the Clock.razor(child):

@code {
    [Parameter]
    public TimeSpan Value { get; set; }
}

<div class="clock">
    <div class="minutes">@this.Value.Minutes</div>
    <div class="min-sec-separator">:</div>
    <div class="seconds">@this.Value.Seconds</div>
</div>

https://blazorrepl.telerik.com/mmFmbRvB17tQXjRy37

For explanation please see @MrCakaShaunCurtis answer.

3
  • if I understand correct, StateHasChanged() at parent level means I'm re-rendering the entire parent component including all the child components of it correct? I only want to re-render Clock component by updating it's value so that other child components won't be bothered with this rendering request... Commented Dec 13, 2022 at 23:09
  • 1
    @maciek - the reason that no rendering occurs is because OnTimerElapsed is a normal event handler, not a UI event - see my answer below for more detail. Commented Dec 13, 2022 at 23:19
  • @MrCakaShaunCurtis You are right here. I tried it out blazorrepl.telerik.com/GmlmbSOX26iuxRWS02 and it does not work as I imagined. I edited my answer and added a reference to your answer.
    – maciek
    Commented Dec 14, 2022 at 9:29
1

You're not setting the parameter on OnTimerElapsed, you're only changing the value of a local field. The parameter is set when the component is rendered. The component will be rendered when the state will change.

You can trigger the state change by calling StateHasChanged() when you need it.

2
  • I think I understand what you are saying. I kinda hoped I don't need to do the StateHasChanged() at the parent component but the clock component would detect as "Value" is a parameter for that component... I could be wrong but wouldn't this essentially re-render the entire parent component meaning it will be not just clock but everything else belong to this parent? Commented Dec 13, 2022 at 23:03
  • call a method on the clock, and that method can call StateHasChanged on the clock only
    – buga
    Commented Dec 14, 2022 at 11:45

Not the answer you're looking for? Browse other questions tagged or ask your own question.