36

I am trying to change a string, being used for a title, in my blazor-server-side application. But I am having trouble getting the UI to update.

I tried using StateHasChanged(), but that didn't work so I looked around and found that on the FlightFinder Demo that was made, it has an OnChange event Action, so I am trying to implement that.

It works until I try to refresh the browser, then I am hit with this error

System.InvalidOperationException: 'The current thread is not associated with the renderer's synchronization context. Use Invoke() or InvokeAsync() to switch execution to the renderer's synchronization context when triggering rendering or modifying any state accessed during rendering.'

This is what I have:

private string _title = "TestSite";
public string Title => _title;

public event Action OnChange;

public void ChangePage(string pageName)
{
   _title = pageName;
   NotifyStateChanged();
}

private void NotifyStateChanged(int navigationType = 0)
{
   OnChange?.Invoke();
}

All I have to do is call ChangePage("some Page Title") and it works, unless as I mentioned I try to refresh.

I am just trying to change a string on one component that through another component, which doesn't sound all that crazy. If there is a better way to do titles or change things from other components, I would love to hear about it.

So, what can I do to make sure that m invoke method is on the correct thread? Or is there a different way to change the title that would be more effective?

Thank you in advance!

DalTron
  • 939
  • 3
  • 9
  • 22
  • 1
    Since this is the google go to for this error I want to post another cause and solution. If your already calling InvokeAsync on a callback and still getting this error it's because the underlying event that raised the invoke is defined as an `Action` when it should be a `Func`. – clamchoda May 31 '22 at 18:07

4 Answers4

54

I have just implemented a State Container like this and ran into the same error - but my service needs to be a singleton. So I found an example on the aspnetcore git that does exactly what the error message says to do. Call InvokeAsync -- not from your state container but when you try to change the state of your razor component.

https://github.com/dotnet/aspnetcore/blob/321db9d99f84cf7a67d453384292d9339de748d1/src/Components/test/testassets/BasicTestApp/DispatchingComponent.razor

So your state container doesn't need to change, just your component event handler does.

@code{
    protected override void OnInitialized()
    {
         _YourService.OnChange += OnMyChangeHandler;
    }

    public void Dispose()
    {
         _YourService.OnChange -= OnMyChangeHandler;
    }

    private async void OnMyChangeHandler(object sender, EventArgs e)
    {
        // InvokeAsync is inherited, it syncs the call back to the render thread
        await InvokeAsync(() => {
            DoStuff();
            StateHasChanged();
        });
    }
}

Now your service (if it's a singleton) can notify ALL your users at once! Think about all hoops we had to jump through in past to do this.

Ravi Makwana
  • 2,782
  • 1
  • 29
  • 41
bmiller
  • 1,454
  • 1
  • 14
  • 14
  • does it work with `public event Action OnChange;` ? – nam Apr 15 '20 at 16:41
  • @bmiller really appreciate your answer on this, it got me out of the weeds about a month ago. I referenced you in this thread: https://stackoverflow.com/questions/61354233/blazor-global-parameter-passing-between-mainlayout-razor-navmenu-razor-co/61367495?noredirect=1#comment108570870_61367495 – Nik P Apr 22 '20 at 20:42
  • 3
    I'm unsure why the lambda expression is needed. This worked for me: `await InvokeAsync(StateHasChanged);` – Rich Armstrong May 01 '20 at 13:07
  • You're right the lamda isn't required. However I think it makes it more obvious what's happening and most likely your "DoStuff()" code might need to run inside the Invoke context as well. – bmiller May 05 '20 at 18:50
  • @nam your service simply invokes the event as usual; `OnChange?.Invoke(this, EventArgs.Empty);` If your service is a singleton every page/user should get the event. – bmiller May 05 '20 at 19:12
  • @bmiller But how does the code in the OnChange Action in your sample code look like? OnChange?.Invoke(); still produces the Exception but somehow the event must be fired. :( – qd0r Jan 14 '21 at 15:37
  • @qd0r the exception may be that you're doing too much in the event. I just edited my handler to include the DoStuff function inside the InvokeAsync. I think ultimately just StateHasChanged() must be called inside the InvokeAsync, but to be careful just put everything in it. Note the event is fired from in the service - maybe in response to another user action. – bmiller Jan 15 '21 at 22:56
19

I posted this first thing in the morning thinking that I wouldn't have the time to look into and thinking that by time someone was able to help me out, I would have found the time to look into it more. Though I have spent a couple of days going back and forth on this already.

I finally found this article that explains that what I am trying to do is called a State Container.

What they said is that I could inject the class as a singleton, which is what I was doing or a scoped service. Turns out all I needed to do was change it to a scoped service and it works great!

DalTron
  • 939
  • 3
  • 9
  • 22
  • 6
    After briefly reading up on scoped vs singleton, this makes perfect sense. I am still going to keep my question up because I don't think blazor has enough questions and answers and this might help someone at some point. – DalTron Jun 06 '19 at 12:55
  • 1
    That worked for me! Thx – Julian Dormon Nov 18 '21 at 20:40
16

no need sophisticated solution, Blazor working perfectly if you will update GUI in your event handler by

this.InvokeAsync(() => this.StateHasChanged());
Viacheslav
  • 1,054
  • 12
  • 16
  • 2
    How is this any different from the answer the OP already got? (Never mind that based on their self-accepted answer, they clearly prefer changing the service type rather than using `InvokeAsync()`). I don't find this answer to be a useful contribution at all. It adds nothing to the information already present. – Peter Duniho Jul 13 '20 at 23:30
  • 1
    It's not DalTron's answer that I'm talking about. Your _"really working code"_ is literally _identical_ to the code posted in bmiller's answer, except for the slight difference that in the other answer, they actually _explain_ the code. – Peter Duniho Jul 17 '20 at 15:22
  • this solution dosen't resolve the problem – Payedimaunt Oct 16 '21 at 21:30
  • 2
    I voted up this answer because @Vaicheslav has exactly 666 points. – Greg Gum Jan 31 '22 at 18:15
  • @PeterDuniho it doesn't need the await operator and the calling function doesn't need to change to async. – clamchoda Apr 04 '22 at 18:14
1

it worked using await InvokeAsync(stateHasChanged); in my case

H H
  • 263,252
  • 30
  • 330
  • 514
  • Yes, that is just a short (and more efficient) form of `InvokeAsync(() => this.StateHasChanged())` – H H Jan 25 '23 at 22:01