I need to update state of blazor component when other component changed

870 views Asked by At

I have a blazor page with different components.

Component structure is the following:

page
   component1
      component11
    component2

When event happens on component11, component2 should update it's state.

On the same component I use OnStateChange event.

For update state of parent component, I can use EventCallback too.

But here I need to pass event two levels up and then down to other component.

Looks a little bit complex. Maybe exist some easy way to do this.

Can event bubbling work automatically? Maybe some application settings?

1

There are 1 answers

0
MrC aka Shaun Curtis On BEST ANSWER

You need to use some form of notification pattern.

Here's a simple provider. Note it requires the WeatherForecastService from DI to demo injecting services.

public class TimeStampProvider
{
    private readonly WeatherForecastService _weatherForecastService;

    public TimeStampProvider(WeatherForecastService weatherForecastService)
        => _weatherForecastService = weatherForecastService;

    public event EventHandler? TimeStampChanged;

    public string TimeStamp = string.Empty;

    public void NotifyTimeStampChanged(string timeStamp)
    {
        TimeStamp = timeStamp;
        this.TimeStampChanged?.Invoke(this, EventArgs.Empty);
    }
}

You can either register this as a scoped DI service or, if you're using it within the scope of a page create an instance in the page component and cascade it.

Next a few components that use TimeStampProvider.

TimeStampSetter

<div class="m-2 p-2">
    <button class="btn btn-primary" @onclick=this.Clicked>Update Time Stamp</button>
</div>

@code {
    [CascadingParameter] private TimeStampProvider myProvider { get; set; } = default!;

    protected override void OnInitialized()
    {
        ArgumentNullException.ThrowIfNull(this.myProvider);
    }
    private void Clicked()
        => myProvider.NotifyTimeStampChanged(DateTime.Now.ToLongTimeString());
}

TimeStampViewer

@implements IDisposable
<div class="bg-dark text-white m-2 p-2">
    <pre>Stamp : @myProvider.TimeStamp </pre>
</div>

@code {
    [CascadingParameter] private TimeStampProvider myProvider { get; set; } = default!;

    protected override void OnInitialized()
    {
        ArgumentNullException.ThrowIfNull(this.myProvider);
        this.myProvider.TimeStampChanged += this.OnChanged;
    }
    private void OnChanged(object? sender, EventArgs e)
        => this.InvokeAsync(StateHasChanged);

    public void Dispose()
        => this.myProvider.TimeStampChanged -= this.OnChanged;
}

MyComponent

<div class="bg-secondary m-3 p-2">
    <h3>MyComponent</h3>
    <TimeStampViewer />
    <TimeStampSetter />
</div>
@code {

}

And final the page, with lots of sub-components to keep in sync. Note the fixed cascading value.

@page "/"
@inject IServiceProvider ServiceProvider

<PageTitle>Index</PageTitle>

<h1>Hello, world!</h1>
<CascadingValue Value=_myStateProvider IsFixed>
    <MyComponent />
    <TimeStampSetter />
    <TimeStampViewer />
    <TimeStampViewer />
    <TimeStampViewer />
    <TimeStampViewer />
    <TimeStampViewer />
    <TimeStampSetter />

</CascadingValue>

@code {
    private TimeStampProvider _myStateProvider = default!;

    protected override void OnInitialized()
    {
        // creates a new instance of TimeStampProvider in the context of the DI service container
        // which populates any DI services in the object
        _myStateProvider = ActivatorUtilities.CreateInstance<TimeStampProvider>(ServiceProvider);

        ArgumentNullException.ThrowIfNull(_myStateProvider);
    }
}

Test it and you'll see everything stays in sync!

enter image description here