What's the canonical way for a Razor page to wait for a Blazor component to finish rendering?

Viewed 2554

Razor components and pages both have an OnAfterRender lifecycle method. When you have <SomeComponent> in your index.razor, the index.razor's OnAfterRender fires first, and then the SomeComponent.OnAfterRender fires afterwards.

Suppose on the razor page you need to do some work, but can only do it after SomeComponent.OnAfterRender fires. What's the appropriate way to do that?

This can be important because a component's backing HTML is not ready (may not exist) until its OnAfterRender fires. So in other words, how can a razor page author know when its components are the equivalent of DOM ready?

1 Answers

You could have your child component expose an Action parameter which it executes after it has rendered, like so.

[Parameter]
public Action AfterRender { get; set; }

protected override OnAfterRender(bool firstRender)
{
  Action?.Invoke();
}

In the parent

<MyChild AfterRender=@SomeMethodInParentComponent/>

I suggest Action instead of EventCallback because of the following process that I suspect might occurr

  1. After EventCallback, Blazor calls StateHasChanged on the callee (the parent)
  2. The parent re-renders
  3. The parent sets parameters on the child (the AfterRender parameter we created)
  4. In case any of the state of the parameters has changed, the child will re-render
  5. Your child component's OnAfterRender method will invoke its AfterRender parameter
  6. Repeat step 1

Using an Action instead of anEventCallback will avoid that situation.

However, if you are passing other parameter values (that might change) from the parent to the child then you should use EventCallback. In this case you'll need to override ShouldRender in your child component so it only renders when the state it works for has actually changed.

You can check if the state has changed by overriding SetParametersAsync and do something like this.

private bool NeedsRendering = true; // Always true for first render

protected override bool ShouldRender => NeedsRendering; // Render only if we say so

protected override void SetParameters(ParameterView parameters)
{
  string oldProperty1 = Property1;
  byte oldSomethingElse = SomethingElse;

  base.SetParameters(parameters);

  if (oldProperty1 != Property1 || oldSomethingElse != somethingElse)
  {
    NeedsRendering = true;
  }
}

protected override async Task OnAfterRenderAsync()
{
  NeedsRendering = false; // We are up to do, not more renders until state changes
  // Do your other stuff
  AfterRender?.Invoke();
}
Related