Components and Parameters

A .razor component takes values in through [Parameter] properties and sends events back out through EventCallback.

Syntax[Parameter] public TValue Name { get; set; }

A component is reusable only if the code around it can tell it what to show. Values flow in through parameters and flow out through callbacks. A parameter is a public C# property marked with the [Parameter] attribute. An attribute is a label in square brackets that marks a member for the framework to find; [Parameter] means "the parent may set this".

Values in

In markup, a parameter looks like an HTML attribute: <StarRating Max="5" />. Give the property a default value and the parameter becomes optional. Mark it [EditorRequired] and the build warns when it is missing. Parameters are set by the parent before each render, which leads to the one rule that matters: never assign to your own parameter property. The parent owns that value and the next render overwrites whatever you stored.

Events out

A child cannot call a method on its parent directly. Instead it declares a parameter of type EventCallback<T>. That type holds a method the parent supplied. The child calls InvokeAsync(value), the parent's method runs, and Blazor re-renders the parent afterwards. Using EventCallback rather than a plain delegate is what triggers that re-render.

Nesting

A parameter named ChildContent of type RenderFragment receives whatever markup is written between the component's opening and closing tags, so a component can wrap other components.

What trips people up

Changing a property inside an object you received as a parameter does not notify the parent. Only the callback does.

Example

Example · csharp
@* Components/StarRating.razor - the child *@

<p>Chosen: @Value of @Max</p>

@for (var i = 1; i <= Max; i++)
{
    var star = i;    // the lambda runs after the loop, so copy the variable
    <button type="button" @onclick="() => OnRated.InvokeAsync(star)">@star</button>
}

@code {
    // Required: the build warns if the parent leaves it out.
    [Parameter, EditorRequired]
    public int Value { get; set; }

    // Optional: the default applies when the parent says nothing.
    [Parameter]
    public int Max { get; set; } = 5;

    // A method handle supplied by the parent. Empty if the parent passed none.
    [Parameter]
    public EventCallback<int> OnRated { get; set; }
}

@* Components/Pages/Feedback.razor - the parent *@
@page "/feedback"
@rendermode InteractiveServer

<StarRating Value="_score" OnRated="Accept" />
<p>You chose @_score.</p>

@code {
    private int _score = 3;

    // Runs when the child invokes OnRated. Blazor re-renders this component
    // when it returns, which re-renders the child with the new Value.
    private void Accept(int score) => _score = score;
}

When to use it

  • Build a table component that takes the rows as a parameter and raises an event when a row is clicked.
  • Wrap a repeated card layout once and pass the inner markup in as child content.
  • Forward class and data- attributes a caller writes on your component straight through to the underlying HTML element.

More examples

Child content and attribute splatting

ChildContent is how a component becomes a container rather than a leaf. CaptureUnmatchedValues plus @attributes keeps you out of the business of declaring a parameter for every HTML attribute a caller might want. Both are needed for any component meant to be used by other teams.

Example · csharp
@* Components/Panel.razor *@

<section @attributes="AdditionalAttributes">
    <h3>@Heading</h3>
    @ChildContent
</section>

@code {
    [Parameter]
    public string Heading { get; set; } = "";

    // Whatever markup the caller writes between <Panel> and </Panel>.
    [Parameter]
    public RenderFragment? ChildContent { get; set; }

    // Collects every attribute that does not match a parameter above,
    // so callers can add class, id, data-* and aria-* themselves.
    [Parameter(CaptureUnmatchedValues = true)]
    public IReadOnlyDictionary<string, object>? AdditionalAttributes { get; set; }
}

@* Used like this: *@
<Panel Heading="Invoices" class="card" data-area="billing">
    <p>Nothing due this month.</p>
    <StarRating Value="4" />
</Panel>

Two-way binding, and the longhand it stands for

@bind-Value is shorthand. The Razor compiler expands it into the Value parameter plus a ValueChanged callback that assigns back to the field. Knowing the long form is what lets you write your own bindable component, and lets you read someone else's when they needed a handler of their own as well.

Example · csharp
@* Components/TextBox.razor *@

<input value="@Value" @onchange="Changed" />

@code {
    [Parameter]
    public string? Value { get; set; }

    // The name must be the parameter name plus "Changed" for @bind-Value to work.
    [Parameter]
    public EventCallback<string?> ValueChanged { get; set; }

    private Task Changed(ChangeEventArgs e) =>
        ValueChanged.InvokeAsync(e.Value?.ToString());
}

@* In the parent, these two lines are the same thing. *@
<TextBox @bind-Value="_name" />

<TextBox Value="_name" ValueChanged="v => _name = v" />

@code {
    private string? _name;
}

@key, so the diff matches the right item

When a list changes, Blazor pairs old render tree entries with new ones by position. @key tells it to pair by identity instead. Use it on every loop that renders components or inputs from a collection that can be reordered or have items removed, otherwise text typed into one row can appear in another.

Example · csharp
@* Components/Pages/Queue.razor *@
@page "/queue"
@rendermode InteractiveServer

@foreach (var job in _jobs)
{
    @* Without @key, removing the first job makes Blazor reuse the first
       element for the second job, carrying over any browser state in it. *@
    <JobRow @key="job.Id" Job="job" OnCancel="Cancel" />
}

@code {
    private readonly List<Job> _jobs =
    [
        new Job(1, "Nightly export"),
        new Job(2, "Invoice run")
    ];

    private void Cancel(int id) => _jobs.RemoveAll(j => j.Id == id);

    public record Job(int Id, string Name);
}

Discussion

  • Be the first to comment on this lesson.