Using Cascading Values in Blazor
There are several instances where it could be cumbersome to pass the same value down to every child component. Think of something like a theme that every component in the entire app might use. It would become painful to pass the theme again and again like that. That's where cascading values come in.
Using cascading values is easy to do. For a single value, you only need to handle the value property of the cascading value and then all of the components inside it can get the value as a parameter. Here's an example of how we could pass a theme down to a Button component from the Counter page:
// Counter.razor
<CascadingValue Value="@Theme">
<Button>Click me!</Button>
</CascadingValue>
@code {
private Theme Theme { get; set; }
}
// Button.razor
<button class="@Theme.ButtonClasses">
@ChildContent
</button>
@code {
[CascadingParameter]
public Theme Theme { get; set; }
[Parameter]
public RenderFragment? ChildContent { get; set; }
}
If the Button component had any children they could also use the same cascading value without making any changes to Button.razor. If you have a component that doesn't use it that's fine. Its children will also be able to access the cascading value the same way.
Multiple Cascading Values and Named Values
You might need to cascade more than one value and you might need the values to be the same type. If you have multiple cascading values but they are a different type, you can name them but you do not have to. If they are the same type you will have to name them, otherwise only the last value will be cascaded properly and any before it won't be passed down. This is how you cascade a named value:
// Counter.razor
<CascadingValue Value="Number" Name="Number">
<CascadingValue Value="Number2" Name="NumberToo">
<Button>Click me</Button>
</CascadingValue>
</CascadingValue>
@code {
private int Number { get; set; } = 4;
private int Number2 { get; set; } = 2;
}
// Button.razor
<button>
@ChildContent @Number @NumberToo
</button>
@code {
[Parameter]
public RenderFragment? ChildContent { get; set; }
[CascadingParameter(Name = "Number")]
public int Number { get; set; }
[CascadingParameter(Name = "NumberToo")]
public int NumberToo { get; set; }
}
The above button would be rendered with the text Click me 4 2
. We can use the name of the property we are using for our cascading value, but we can name it any legal C# name we want. Again, this is only necessary if we have multiple cascading values of the same type, but using named values can be beneficial because if you need another cascading value of the same type halfway through the project you won't have to go back and make the changes necessary to make it a named value.
Unchanging Values
Sometimes cascading values are needed but they never change. If you have a cascading value that never changes (again, a theme is a great example here) you will want to set the IsFixed
property to true
:
<CascadingValue Value="Theme" Name="Theme" IsFixed="true">
<SomeChildComponent />
</CascadingValue>
@code {
private Theme Theme { get; set; }
}
Why Set IsFixed?
For cascading values that never change, we should set IsFixed="true"
. Setting this property tells Blazor that the value never changes so it doesn't need to watch the value. Particularly when several child components use the value, not setting IsFixed can result in degraded performance.
Using Cascading Values to Require Parent Components
This is an interesting use case for cascading values. You may have noticed that several of the Input components require that they be inside an EditForm component. We can enforce a similar behavior by using a cascading value. We just need to take the cascading value and check to see if it was passed in during OnInitialized and throw and error if it was not.
// MyForm.razor
<form>
<CascadingValue Value="context" Name="EditContext">
@ChildContent
</CascadingValue>
</form>
@code {
private MyFormContext context { get; set; } = new();
[Parameter]
public RenderFragment? ChildContent { get; set; }
}
// MyFormInput.razor
<input type="text" value="@Text"/>
@code {
[Parameter]
public string Text { get; set; } = string.Empty;
[CascadingParameter(Name = "EditContext")]
public MyEditContext? Context { get; set; }
protected override void OnInitialized()
{
base.OnInitialized();
if (Context is null)
{
throw new Exception("MyFormInput cannot be instantiated outside of a MyForm");
}
}
}
// Index.razor
// Happy day, no errors!
<MyForm>
<MyFormInput Text="Hi mom!"/>
</MyForm>
// Throws an exception when it loads
<MyFormInput Text="Sad face" />
Now if we try to use a MyFormInput
outside of a MyForm
an error will be thrown. The intent here would be something like a form where all of the components are working together in a particular context toward the same goal. Another example could be with an SVG. You wouldn't generally want to try to use a circle
outside of an SVG because it wouldn't mean anything and could cause unexpected behavior.
Did you find this useful? Let me know and as always, happy coding!
Subscribe to my newsletter
Read articles from Ryan Phillips directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Ryan Phillips
Ryan Phillips
I've been developing software for over a decade and I want to help other devs overcome some of the challenges that come with the job.