Components and Parameters
A .razor component takes values in through [Parameter] properties and sends events back out through EventCallback.
[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
* Components/StarRating.razor - the child *
<p>Chosen: of </p>
(var i = 1; i <= Max; i++)
{
var star = i; // the lambda runs after the loop, so copy the variable
<button type="button" ="() => OnRated.InvokeAsync(star)"></button>
}
{
// 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 *
"/feedback"
InteractiveServer
<StarRating Value="_score" OnRated="Accept" />
<p>You chose .</p>
{
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.
@* 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.
@* 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.
@* 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