Previously, we moved ClinicApp's patient list into a PatientService and learned how dependency injection hands it to any component that asks. Today we answer a question that has quietly followed us since Part 1: when does a component actually run its code?

The mystery: where do I load my data?

Imagine ClinicApp no longer keeps patients in memory but fetches them from a database or a web API. That fetch takes time. So… where does the fetching code go? In the constructor? Somewhere in the markup? The answer is that every Blazor component lives through a lifecycle — a fixed sequence of stages, each with its own job — and Blazor gives you a method to override at each stage. Once you know the sequence, "where do I put this code?" almost answers itself.

SetParametersAsync parameters arrive OnInitialized(Async) first render only — load data here the welcome party OnParametersSet(Async) runs on every change Render markup becomes HTML OnAfterRender(Async) the DOM exists now re-renders
What runs when: OnInitialized greets the component once; OnParametersSet and Render repeat as things change.

Think of it like a patient visiting the clinic: check-in, initial consultation, follow-up visits, and the moment they finally walk out the door into the waiting room. Let's walk the stages in order.

SetParametersAsync — the parameters arrive

Before anything else, Blazor delivers the component's parameters — the values a parent passes in, like the Patient we hand to PatientCard, or the Id that PatientDetails reads from the URL. You will almost never override this method yourself; just know it runs first, so by the next stage your parameters are ready to use.

OnInitialized and OnInitializedAsync — once per component life

This pair runs once, right after the component is created and its first parameters are set. It is the place to load initial data. Need to call a service? Fetch from an API? Do it in OnInitializedAsync. The Async version exists so you can await slow work without freezing anything — if await still feels fuzzy, our async/await explainer has your back.

OnParametersSet and OnParametersSetAsync — every time parameters change

Here's a subtle one. When you navigate from /patients/3 to /patients/4, Blazor doesn't throw away your PatientDetails component and build a new one — it reuses it and simply hands it the new Id. That means OnInitializedAsync does not run again! If you loaded patient 3 there, you'd be staring at patient 3 forever. OnParametersSetAsync runs every time parameters change, so it's the right home for "re-load whatever depends on my parameters."

Render — Blazor draws the component

Next, Blazor turns your markup into UI. You don't write code for this stage — it just happens, automatically, whenever Blazor believes something on screen may have changed.

OnAfterRender and OnAfterRenderAsync — the DOM exists now

Finally, after the component has been drawn, this pair fires. Its superpower is that the actual HTML elements exist in the browser at this point, which matters mostly for JavaScript-ish work: focusing an input, measuring an element, starting a chart library. It's the rarest one to need — a handy first parameter, firstRender, tells you whether this is the very first paint.

The loading-state pattern

Now let's cash in that knowledge. Slow data plus instant rendering means there's a moment where the page is visible but the data isn't there yet. The fix is a simple, beloved pattern: a loading flag.

@page "/patients"
@rendermode InteractiveServer
@inject PatientService Patients

<h2>Our patients</h2>

@if (loading)
{
    <p>Loading patients…</p>
}
else
{
    @foreach (var patient in patients)
    {
        <PatientCard Patient="patient" />
    }
}

@code {
    private List<Patient> patients = [];
    private bool loading = true;

    protected override async Task OnInitializedAsync()
    {
        patients = await Patients.GetPatientsAsync();
        loading = false;
    }
}

The flow: the component initializes, loading starts as true, and Blazor renders the "Loading…" message immediately. When the awaited call finishes, we flip loading to false, Blazor re-renders, and the real list appears. Your users see instant feedback instead of a blank stare.

Tip: This little pattern — bool loading = true;, load in OnInitializedAsync, flip the flag — appears in almost every real Blazor app you will ever read. Learn it once, spot it everywhere.

One honest paragraph about StateHasChanged

You may have seen StateHasChanged() in other tutorials and wondered if you should be sprinkling it everywhere. You shouldn't. Blazor automatically re-renders after the things it can see: button clicks, form input, lifecycle methods finishing. You only need to call StateHasChanged when data changes outside that flow — say, a timer ticks every second, or a background task pushes an update. In those cases you're tapping Blazor on the shoulder: "hey, something changed, please redraw." That's all it is.

Common mistakes to dodge

  • Loading data in the constructor. Constructors can't await, and parameters haven't arrived yet. Use OnInitializedAsync instead.
  • Mixing up the Async suffix duo. Writing awaited work inside OnInitialized (the sync one) won't compile the way you hope — and defining OnInitializedAsync without override compiles fine but silently never runs. Double-check both the suffix and the override keyword.
  • Calling StateHasChanged in OnAfterRender. That triggers a re-render, which triggers OnAfterRender again, which… congratulations, you've built an infinite loop.

Note: This is settled knowledge — the lifecycle works exactly the same in current Blazor (.NET 10) — and you don't need to memorize every stage today. Remember two: OnInitializedAsync for "load my data once," and OnParametersSetAsync for "my parameters changed, react." Those two cover the vast majority of real-world needs.

ClinicApp now knows exactly when to load its data. Next up, we'll give it something real to load: in Part 11 we call an actual web API over the internet with HttpClient — and the loading pattern you just learned will be doing the heavy lifting. See you there!