BlazorServer call component method cause System.NullReferenceException in OnAfterRenderAsync(bool firstrender)

137 Views Asked by At

I have a blazor page which I could call like :

@page "/master/{Id:int}"
@page "/master/{Id:int}/todo/{TodoId:int}"

on this page I have this component in one of my tab on that page :

<CompTodo ParentId="@Id.Value" @ref="compTodo" />

in code :

CompTodo compTodo; 
.. 
.. 
protected override async Task OnAfterRenderAsync(bool firstRender) {
    if (firstRender)
    {
        if (TodoId != null)
        {
            TabsSelectedIndex = 3 // change tabindex to Todoview
            await compTodo.TaskEdit(TodoId.Value)
        }
    } 
}

This compTodo.TaskEdit() opens one edit dialog in my CompTodo component. I have a list of the users next 5 todos on my masterpage where I have a butten on each row that call compTodo.TaskEdit(rowid) that works as it should.

But I have a todolist on another page that shows all todos a user have, not only under one specific master so therefor I will have ability to call this method from another place on my site.

But the row await compTodo.TaskEdit() throw "System.NullReferenceException" when I place in firstRender, I guess it's because my CompTodo isn't load completely? So I try to use if(!firstRender), then I got my dialog displayed but it continues open in an endless loop.

Have tried to have a parameter int RenderCount

and under OnAfterRenderAsync

{
  if (firstRender) { RenderCount = 0 }
  if (RenderCount == 1) { call my method }
  RenderCount++;
}

But this also call my component method endless times.

How can I call a method in an childcomponent only once and first after all components are loaded?

1

There are 1 best solutions below

1
On

I'm not sure if this is on the right track for you, but the code below is a simple demonstration of cascading a context object that can be used to communicate between components: it's how EditForm works. It may help you solve your problem. I'll delete the answer if it's not helpful.

First a simple context class with an event.

public class MyContext
{
    public bool Loading { get; set; }

    public event EventHandler? OnCompletion;

    public void NotifyCompletion(object? sender, EventArgs e)
        => OnCompletion?.Invoke(sender, e);
}

A simple component to interact with the context:

<h3>MyComponent</h3>

<div class="@_loadingCss">
    @_loadingText
</div>

@code {
    [CascadingParameter] private MyContext myContext { get; set; } = new();

    private string _loadingCss => myContext.Loading ? "alert alert-danger" : "alert alert-primary";
    private string _loadingText => myContext.Loading ? "Loading" : "Loaded";

    protected override async Task OnInitializedAsync()
    {
        myContext.Loading = true;
        // Emulate some slow async activity taking it's time loading!
        await Task.Delay(2000);
        myContext.Loading = false;
        myContext.NotifyCompletion(null, EventArgs.Empty);
    }
}

And a demo page:

@page "/"
@implements IDisposable

<PageTitle>Index</PageTitle>

<h1>Hello Blazor</h1>
<CascadingValue Value="this.myContext">
    <MyComponent />
</CascadingValue>

<div class="@_loadingCss">
    @_loadingText
</div>

@code {
    private string _loadingCss => myContext.Loading ? "alert alert-danger" : "alert alert-primary";
    private string _loadingText => myContext.Loading ? "Loading" : "Loaded";
    private MyContext myContext = new();

    protected override void OnInitialized()
    {
        myContext.Loading = true;
        myContext.OnCompletion += this.OnCompletion;
    }

    private void OnCompletion(object? sender, EventArgs e)
        => StateHasChanged();

    public void Dispose()
        => myContext.OnCompletion -= this.OnCompletion;
}