Previously, you built the /patients page: a seeded list of patients rendered with @foreach, a friendly empty state, and live search.

Our patient cards look great, but they're mute. Click one and… nothing. The card has no way to tell the page "hey, the user picked me." Today we fix that, and along the way you learn the single most important pattern in component-based UI. Ready?

The rule of the river

Think of your component tree as a river system. Data flows down: a parent hands values to its children through parameters, the way a river feeds its streams. And news flows up: when something happens inside a child, it raises an event, like a message in a bottle floating back upstream. Children never reach into their parent and change things directly — they announce, and the parent decides what to do.

Parent component Patients.razor Child component PatientCard.razor [Parameter] data flows down EventCallback events flow up
The rule of the river: data flows down through parameters, and news travels back up through EventCallback.

You already know half of this river. PatientCard has taken parameters since Part 3 — that's the downstream half. Today we build the upstream half: an EventCallback.

One quick housekeeping move

In Part 5, the Patient class lived inside Patients.razor. Now that PatientCard needs to know what a Patient is too, cut the class out of the page's @code block and paste it into its own file, Patient.cs, at the root of ClinicApp (same folder as Program.cs). Same three properties — Id, Name, Age — just in a spot every component can see.

Teaching PatientCard to speak

We'll upgrade the card in two ways. First, instead of separate Name and Age parameters, it now takes the whole Patient object — one parameter, everything included. Second, it gets an OnSelected callback and a button that fires it. Here's the new PatientCard.razor:

<div class="patient-card">
    <h3>@Patient.Name</h3>
    <p>Age: @Patient.Age</p>
    <button @onclick="SelectMe">Select</button>
</div>

@code {
    [Parameter]
    public Patient Patient { get; set; } = new();

    [Parameter]
    public EventCallback<Patient> OnSelected { get; set; }

    private Task SelectMe() => OnSelected.InvokeAsync(Patient);
}

Read the upstream path carefully: the button click runs SelectMe, which calls OnSelected.InvokeAsync(Patient). Translation: "whoever is listening — the user selected this patient." The card doesn't know who's listening or what they'll do with the news. It just announces. That ignorance is a feature: it keeps the card reusable anywhere.

The parent listens

Now the page's turn. In Patients.razor, pass each patient down and wire a handler to OnSelected — then show a little banner when someone's been picked:

@if (selected is not null)
{
    <p><strong>Now seeing:</strong> @selected.Name (age @selected.Age)</p>
}

@foreach (var patient in FilteredPatients)
{
    <PatientCard Patient="patient" OnSelected="ShowSelected" />
}

@code {
    private Patient? selected;

    private void ShowSelected(Patient patient)
    {
        selected = patient;
    }
}

Run it and click a card's button. The banner appears with that patient's details. Trace the river: the page hands patient down into each card; the click floats the same patient back up through OnSelected; the parent updates its selected state; Blazor re-renders. It's the Part 4 loop again — event, handler, state, re-render — just stretched across two components.

Tip: Name your callback parameters OnSomethingOnSelected, OnDeleted, OnSaved. Anyone reading <PatientCard OnSelected="ShowSelected" /> instantly knows which way the river flows.

Why EventCallback and not a plain Action?

C# veterans might wonder: couldn't the parameter just be an Action<Patient>? It could compile, but you'd lose the magic. In current Blazor (.NET 10), EventCallback is wired into the render loop: after the parent's handler runs, Blazor automatically re-renders so the UI reflects the new state — that's why our banner appeared without any extra code. It also handles async handlers gracefully and doesn't explode if the parent never assigns it. Plain Action gives you none of that. Rule of thumb: for component parameters, it's EventCallback every time.

A taste of CascadingValue

Parameters are perfect for parent-to-child handoffs. But what about data that everyone needs — the clinic's name, a color theme, the signed-in user? Passing it down through five layers of parameters gets old fast. For that, Blazor offers CascadingValue: wrap a section of your app, and any descendant can opt in.

<CascadingValue Value="@clinicName">
    <PatientCard Patient="patient" OnSelected="ShowSelected" />
</CascadingValue>

@code {
    private string clinicName = "Sunrise Clinic";
}

Any component inside the wrapper — however deep — can receive it like this:

@code {
    [CascadingParameter]
    public string? ClinicName { get; set; }
}

Note: Consider this intermediate seasoning — file it away rather than reaching for it daily. Regular parameters and callbacks cover the vast majority of component communication, and overusing cascading values makes it hard to see where data comes from. When "everyone needs this" is genuinely true, though, it's the right tool.

Next up: forms done properly

Our patients can now be listed, searched, and selected — but they're all hardcoded. A real clinic checks in new patients, and that means forms: inputs, submit buttons, and validation that politely catches mistakes. That's Part 7, and it's where ClinicApp starts feeling like a real app. See you there!