# CascadingValueProvider
## Description
BitCascadingValueProvider replaces a stack of nested CascadingValue components with a single list, so a whole set of values reaches the descendant components from one place. Each value carries its own cascaded type, an optional name and the IsFixed and Enabled flags, and can be given eagerly, deferred until it is needed, re-read on every render or told to watch itself for changes. The list is written inline with the Values parameter or built up with the ValueList parameter, a later value shadows an earlier one of the same type or name, and changing a value refreshes the consumers on its own.
## Notes
Every value here becomes a real CascadingValue component, so everything that holds for a hand-written one holds here too: consumers match by type and by name, a value that changes re-renders only the components that consume it, and an IsFixed value is never watched. Reach for a root-level cascading value (AddCascadingValue on the service collection) instead when the same value has to reach every component of the app rather than one subtree.
## Parameters
| Name | Type | Default Value | Description |
| :--- | :--- | :------------ | :---------- |
| ChildContent | `RenderFragment?` | null | The content to which the values should be provided. |
| Values | `IEnumerable<BitCascadingValue>?` | null | The cascading values to be provided for the children. These values are provided after (so they take precedence over) the ones of the ValueList parameter. |
| ValueList | `BitCascadingValueList?` | null | The cascading value list to be provided for the children. These values are provided before (so they can be overridden by) the ones of the Values parameter. |
## Sub Classes
### BitCascadingValue Properties
Defines a value that can be cascaded to descendant components.
| Name | Type | Default Value | Description |
| :--- | :--- | :------------ | :---------- |
| Value | `object?` | null | The value to be provided. Assigning a value that is not compatible with the ValueType throws an ArgumentException, and assigning a different value raises the Changed event. A lazy factory runs the first time it is read, a computed one on every read. |
| Name | `string?` | null | The optional name of the cascading value. An empty or white-space name is treated as no name at all, and the consumers match it case-insensitively. Renaming a live value re-creates the underlying CascadingValue component so that the consumers are matched again under the new name. |
| IsFixed | `bool` | false | If true, indicates that Value will not change, so consumers are not subscribed for change notifications. Toggling it re-creates the underlying CascadingValue component. |
| Enabled | `bool` | true | Determines whether this cascading value is provided to the children. A disabled value is skipped as if it was never added, so an outer or root level cascading value of the same type or name shows through. |
| AutoNotify | `bool` | false | Watches the cascaded value itself, so an INotifyPropertyChanged or INotifyCollectionChanged value raises Changed on its own. The subscription is only held while a provider is listening, so the cascaded object never keeps this value alive. |
| ValueType | `Type` | Value?.GetType() | The type to use as the TValue of the CascadingValue component. It is read-only and defaults to the runtime type of the value, so it must be provided explicitly for null values, nullable value types, base types and interfaces. |
| IsValueCreated | `bool` | true | Whether the value is already available. It is only false for a lazily created value whose factory has not run yet. |
| IsComputed | `bool` | false | Whether the value is produced by a factory that runs on every read rather than being stored once, which is what the Computed factory methods create. |
| Changed | `event Action<BitCascadingValue>?` | | Raised whenever the value changes, which is what lets the hosting BitCascadingValueProvider re-render and push the new value down to the consumers on its own. |
| ChangedAsync | `event Func<BitCascadingValue, Task>?` | | The awaitable counterpart of Changed, which is what the provider subscribes to and what makes NotifyChangedAsync complete only once the re-render is done. |
| NotifyChanged() | `void` | | Raises the Changed and ChangedAsync events on demand, which is how a cascaded object that is mutated in place is pushed down to the consumers. |
| NotifyChangedAsync() | `Task` | | The awaitable form of NotifyChanged, whose task completes once every listening provider has re-rendered, like CascadingValueSource.NotifyChangedAsync does. |
| From<T>(T value, string? name = null, bool isFixed = false, bool enabled = true) | `BitCascadingValue` | | Creates a cascading value whose ValueType is the static type of T. |
| Fixed<T>(T value, string? name = null, bool enabled = true) | `BitCascadingValue` | | Creates a fixed (IsFixed) cascading value whose ValueType is the static type of T. |
| Lazy<T>(Func<T> valueFactory, string? name = null, bool isFixed = false, bool enabled = true) | `BitCascadingValue` | | Creates a cascading value whose value is produced by the factory the first time it is actually needed, so a disabled or shadowed value is never built. The factory runs at most once. |
| Computed<T>(Func<T> valueFactory, string? name = null, bool isFixed = false) | `BitCascadingValue` | | Creates a cascading value that is re-read from the factory every time it is provided, so one long lived value keeps tracking the state it is derived from. |
| Observed<T>(T value, string? name = null, bool enabled = true) | `BitCascadingValue` | | Creates a cascading value with AutoNotify turned on, so a value reporting its own mutations refreshes the consumers without any call to NotifyChanged. |
### BitCascadingValueList Properties
A helper class to ease the using of a list of the BitCascadingValue.
| Name | Type | Default Value | Description |
| :--- | :--- | :------------ | :---------- |
| Add<T>(T value, string? name = null, bool isFixed = false, bool enabled = true) | `void` | | Adds a typed BitCascadingValue to the list, cascading the value as the static type of T. |
| Add(BitCascadingValue? value) | `void` | | Adds an already created BitCascadingValue to the list. A null item is ignored. |
| Add(object? value, Type valueType, string? name = null, bool isFixed = false, bool enabled = true) | `void` | | Adds a BitCascadingValue with an explicit ValueType to the list, for when the cascaded type is only known at runtime. |
| AddIf<T>(bool condition, T value, string? name = null, bool isFixed = false, bool enabled = true) | `void` | | Adds a typed BitCascadingValue to the list only when the given condition is true. |
| AddIf(bool condition, BitCascadingValue? value) | `void` | | Adds an already created BitCascadingValue to the list only when the given condition is true, which paired with a lazy value keeps the value of a conditional entry from being built at all. |
| AddFixed<T>(T value, string? name = null) | `void` | | Adds a fixed (IsFixed) typed BitCascadingValue to the list. |
| AddFixed(object? value, Type valueType, string? name = null) | `void` | | Adds a fixed (IsFixed) BitCascadingValue with an explicit ValueType to the list. |
| AddLazy<T>(Func<T> valueFactory, string? name = null, bool isFixed = false, bool enabled = true) | `void` | | Adds a typed BitCascadingValue whose value is produced by the factory the first time it is actually needed. The factory runs at most once. An overload taking an explicit ValueType is available as well. |
| AddComputed<T>(Func<T> valueFactory, string? name = null, bool isFixed = false) | `void` | | Adds a typed BitCascadingValue that is re-read from the factory on every render, so a list built once keeps tracking the state its values are derived from. An overload taking an explicit ValueType is available as well. |
| AddObserved<T>(T value, string? name = null, bool enabled = true) | `void` | | Adds a typed BitCascadingValue that watches the value itself, so an INotifyPropertyChanged or INotifyCollectionChanged object refreshes the consumers on its own. |
| Find<T>(string? name = null) | `BitCascadingValue?` | | Finds the entry that the given type and name resolve to, which is the last one matching both, since that is the one shadowing all the others. An overload taking an explicit ValueType is available as well. |
| Contains<T>(string? name = null) | `bool` | | Whether the list holds an entry of the static type of T carrying the given name, regardless of whether it is enabled. |
| Remove<T>(string? name = null) | `bool` | | Removes every entry of the static type of T carrying the given name, and reports whether anything was removed. An overload taking an explicit ValueType is available as well. |
| Set<T>(T value, string? name = null, bool isFixed = false, bool enabled = true) | `void` | | Replaces every entry of the static type of T carrying the given name with a new one, or adds it when the list has none, so the list ends up with exactly one entry per type and name. |
## Examples
\n**Basic**:
```razor
```
`CascadingValueDemoConsumer.razor`:
```razor
@code {
[Parameter] public string? Title { get; set; }
[CascadingParameter(Name = "Theme")]
public string? Theme { get; set; }
[CascadingParameter(Name = "NotificationCount")]
public int? NotificationCount { get; set; }
[CascadingParameter(Name = "IsAuthenticated")]
public bool? IsAuthenticated { get; set; }
[CascadingParameter(Name = "NamedUser")]
public CascadingDemoUser? NamedUser { get; set; }
[CascadingParameter]
public CascadingDemoUser? TypedUser { get; set; }
private static string? FormatUser(CascadingDemoUser? user) => user is null ? null : $"{user.Name} [{user.Role}]";
}
```
`CascadingDemoUser.cs`:
```csharp
public sealed record CascadingDemoUser(string Name, string Role);
```
\n**Values**:
```razor
Switch to @nextTheme theme
Add notification (@notificationCount)
```
```csharp
private bool isAuthenticated = true;
private string currentTheme = "Light";
private int notificationCount = 2;
private string userName = "Ava Smith";
private string userRole = "Product manager";
private string nextTheme => currentTheme == "Light" ? "Dark" : "Light";
private IEnumerable values =>
[
(currentTheme, "Theme"),
(isAuthenticated, "IsAuthenticated"),
(notificationCount, "NotificationCount"),
new (new CascadingDemoUser("Saleh Xafan", "CTO"), "NamedUser"),
new (new CascadingDemoUser(userName, userRole))
];
```
`CascadingValueDemoConsumer.razor`:
```razor
@code {
[Parameter] public string? Title { get; set; }
[CascadingParameter(Name = "Theme")]
public string? Theme { get; set; }
[CascadingParameter(Name = "NotificationCount")]
public int? NotificationCount { get; set; }
[CascadingParameter(Name = "IsAuthenticated")]
public bool? IsAuthenticated { get; set; }
[CascadingParameter(Name = "NamedUser")]
public CascadingDemoUser? NamedUser { get; set; }
[CascadingParameter]
public CascadingDemoUser? TypedUser { get; set; }
private static string? FormatUser(CascadingDemoUser? user) => user is null ? null : $"{user.Name} [{user.Role}]";
}
```
`CascadingDemoUser.cs`:
```csharp
public sealed record CascadingDemoUser(string Name, string Role);
```
\n**ValueList**:
```razor
```
```csharp
private readonly string? nullableTheme = null;
private readonly bool? nullableIsAuthenticated = null;
private readonly int? nullableNotificationCount = null;
private readonly CascadingDemoUser? nullableNamedUser = null;
private readonly CascadingDemoUser? nullableTypedUser = null;
```
`CascadingValueDemoConsumer.razor`:
```razor
@code {
[Parameter] public string? Title { get; set; }
[CascadingParameter(Name = "Theme")]
public string? Theme { get; set; }
[CascadingParameter(Name = "NotificationCount")]
public int? NotificationCount { get; set; }
[CascadingParameter(Name = "IsAuthenticated")]
public bool? IsAuthenticated { get; set; }
[CascadingParameter(Name = "NamedUser")]
public CascadingDemoUser? NamedUser { get; set; }
[CascadingParameter]
public CascadingDemoUser? TypedUser { get; set; }
private static string? FormatUser(CascadingDemoUser? user) => user is null ? null : $"{user.Name} [{user.Role}]";
}
```
`CascadingDemoUser.cs`:
```csharp
public sealed record CascadingDemoUser(string Name, string Role);
```
\n**Nesting**:
```razor
```
`CascadingValueDemoConsumer.razor`:
```razor
@code {
[Parameter] public string? Title { get; set; }
[CascadingParameter(Name = "Theme")]
public string? Theme { get; set; }
[CascadingParameter(Name = "NotificationCount")]
public int? NotificationCount { get; set; }
[CascadingParameter(Name = "IsAuthenticated")]
public bool? IsAuthenticated { get; set; }
[CascadingParameter(Name = "NamedUser")]
public CascadingDemoUser? NamedUser { get; set; }
[CascadingParameter]
public CascadingDemoUser? TypedUser { get; set; }
private static string? FormatUser(CascadingDemoUser? user) => user is null ? null : $"{user.Name} [{user.Role}]";
}
```
`CascadingDemoUser.cs`:
```csharp
public sealed record CascadingDemoUser(string Name, string Role);
```
\n**ValueType**:
```razor
```
```csharp
private readonly IEnumerable nullCountValues = [BitCascadingValue.From(null)];
```
`CascadingValueDemoTypeConsumer.razor`:
```razor
```
```csharp
private readonly IEnumerable fixedValues =
[
BitCascadingValue.Fixed("Light", "Theme"),
BitCascadingValue.Fixed((3) as int?, "NotificationCount"),
BitCascadingValue.Fixed(new CascadingDemoUser("Yaser Moradi", "CEO"))
];
```
`CascadingValueDemoConsumer.razor`:
```razor
@code {
[Parameter] public string? Title { get; set; }
[CascadingParameter(Name = "Theme")]
public string? Theme { get; set; }
[CascadingParameter(Name = "NotificationCount")]
public int? NotificationCount { get; set; }
[CascadingParameter(Name = "IsAuthenticated")]
public bool? IsAuthenticated { get; set; }
[CascadingParameter(Name = "NamedUser")]
public CascadingDemoUser? NamedUser { get; set; }
[CascadingParameter]
public CascadingDemoUser? TypedUser { get; set; }
private static string? FormatUser(CascadingDemoUser? user) => user is null ? null : $"{user.Name} [{user.Role}]";
}
```
`CascadingDemoUser.cs`:
```csharp
public sealed record CascadingDemoUser(string Name, string Role);
```
\n**Enabled**:
```razor
```
```csharp
private bool provideTheme = true;
private bool provideUser = true;
private IEnumerable conditionalValues =>
[
new("Dark", "Theme") { Enabled = provideTheme },
new(new CascadingDemoUser("Ava Smith", "Product manager")) { Enabled = provideUser }
];
```
`CascadingValueDemoConsumer.razor`:
```razor
@code {
[Parameter] public string? Title { get; set; }
[CascadingParameter(Name = "Theme")]
public string? Theme { get; set; }
[CascadingParameter(Name = "NotificationCount")]
public int? NotificationCount { get; set; }
[CascadingParameter(Name = "IsAuthenticated")]
public bool? IsAuthenticated { get; set; }
[CascadingParameter(Name = "NamedUser")]
public CascadingDemoUser? NamedUser { get; set; }
[CascadingParameter]
public CascadingDemoUser? TypedUser { get; set; }
private static string? FormatUser(CascadingDemoUser? user) => user is null ? null : $"{user.Name} [{user.Role}]";
}
```
`CascadingDemoUser.cs`:
```csharp
public sealed record CascadingDemoUser(string Name, string Role);
```
\n**Notifications**:
```razor
Run a background job
```
```csharp
private bool jobIsRunning;
private readonly CascadingDemoStatus jobStatus = new();
private readonly BitCascadingValue jobStatusValue;
private readonly BitCascadingValue jobProgressValue;
private readonly IEnumerable notifyingValues;
public MyPage()
{
jobStatusValue = BitCascadingValue.From(jobStatus);
jobProgressValue = BitCascadingValue.From(null, "Progress");
notifyingValues = [jobStatusValue, jobProgressValue];
}
private void RunBackgroundJob()
{
if (jobIsRunning) return;
jobIsRunning = true;
_ = Task.Run(async () =>
{
try
{
// The cascaded status object is mutated in place, so there is no assignment to notice.
jobStatus.Text = "Running";
await jobStatusValue.NotifyChangedAsync();
for (var i = 1; i <= 5; i++)
{
await Task.Delay(500);
// Assigning the Value raises the Changed event on its own.
jobProgressValue.Value = i;
}
jobStatus.Text = "Done";
await jobStatusValue.NotifyChangedAsync();
}
finally
{
jobIsRunning = false;
}
});
}
```
`CascadingValueDemoStatusConsumer.razor`:
```razor
@code {
[Parameter] public string? Title { get; set; }
[CascadingParameter]
public CascadingDemoStatus? Status { get; set; }
[CascadingParameter(Name = "Progress")]
public int? Progress { get; set; }
}
```
`CascadingDemoStatus.cs`:
```csharp
///
/// A mutable state holder, cascaded as a single instance that is updated in place, which is the case
/// that BitCascadingValue.NotifyChanged exists for.
///
public sealed class CascadingDemoStatus
{
public string Text { get; set; } = "Idle";
}
```
\n**Auto notifications**:
```razor
Run a background job
```
```csharp
private bool observableJobIsRunning;
private readonly CascadingDemoObservableStatus observableStatus = new();
private readonly IEnumerable observableValues;
public MyPage()
{
observableValues = [BitCascadingValue.Observed(observableStatus)];
}
private void RunObservableJob()
{
if (observableJobIsRunning) return;
observableJobIsRunning = true;
_ = Task.Run(async () =>
{
try
{
// Nothing here notifies anything: the status object reports its own changes.
observableStatus.Text = "Running";
for (var i = 1; i <= 5; i++)
{
await Task.Delay(500);
observableStatus.Count = i;
}
observableStatus.Text = "Done";
}
finally
{
observableJobIsRunning = false;
}
});
}
```
`CascadingValueDemoObservableConsumer.razor`:
```razor
@code {
[Parameter] public string? Title { get; set; }
[CascadingParameter]
public CascadingDemoObservableStatus? Status { get; set; }
}
```
`CascadingDemoObservableStatus.cs`:
```csharp
using System.ComponentModel;
using System.Runtime.CompilerServices;
///
/// A state holder that reports its own mutations, which is what BitCascadingValue.Observed watches so that
/// the consumers refresh without a single call to NotifyChanged.
///
public sealed class CascadingDemoObservableStatus : INotifyPropertyChanged
{
private int _count;
private string _text = "Idle";
public event PropertyChangedEventHandler? PropertyChanged;
public string Text
{
get => _text;
set
{
if (_text == value) return;
_text = value;
OnPropertyChanged();
}
}
public int Count
{
get => _count;
set
{
if (_count == value) return;
_count = value;
OnPropertyChanged();
}
}
private void OnPropertyChanged([CallerMemberName] string? propertyName = null)
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
```
\n**Lazy values**:
```razor
```
```csharp
private int lazyUserFactoryCalls;
private readonly BitCascadingValue lazyTypedUser;
private readonly BitCascadingValue lazyNamedUser;
private readonly IEnumerable lazyValues;
public MyPage()
{
lazyTypedUser = BitCascadingValue.Lazy(() => CreateLazyUser("Ava Smith", "Product manager"));
lazyNamedUser = BitCascadingValue.Lazy(() => CreateLazyUser("Saleh Xafan", "CTO"), "NamedUser", enabled: false);
lazyValues = [lazyTypedUser, lazyNamedUser];
}
private bool provideLazyNamedUser
{
get => lazyNamedUser.Enabled;
set => lazyNamedUser.Enabled = value;
}
private CascadingDemoUser CreateLazyUser(string name, string role)
{
lazyUserFactoryCalls++;
return new CascadingDemoUser(name, role);
}
```
`CascadingValueDemoConsumer.razor`:
```razor
@code {
[Parameter] public string? Title { get; set; }
[CascadingParameter(Name = "Theme")]
public string? Theme { get; set; }
[CascadingParameter(Name = "NotificationCount")]
public int? NotificationCount { get; set; }
[CascadingParameter(Name = "IsAuthenticated")]
public bool? IsAuthenticated { get; set; }
[CascadingParameter(Name = "NamedUser")]
public CascadingDemoUser? NamedUser { get; set; }
[CascadingParameter]
public CascadingDemoUser? TypedUser { get; set; }
private static string? FormatUser(CascadingDemoUser? user) => user is null ? null : $"{user.Name} [{user.Role}]";
}
```
`CascadingDemoUser.cs`:
```csharp
public sealed record CascadingDemoUser(string Name, string Role);
```
\n**Computed values**:
```razor
Click me (@computedClicks)
```
```csharp
private int computedClicks;
private readonly IEnumerable computedValues;
public MyPage()
{
computedValues =
[
BitCascadingValue.Computed(() => computedClicks % 2 == 0 ? "Light" : "Dark", "Theme"),
BitCascadingValue.Computed(() => computedClicks, "NotificationCount")
];
}
```
`CascadingValueDemoConsumer.razor`:
```razor
@code {
[Parameter] public string? Title { get; set; }
[CascadingParameter(Name = "Theme")]
public string? Theme { get; set; }
[CascadingParameter(Name = "NotificationCount")]
public int? NotificationCount { get; set; }
[CascadingParameter(Name = "IsAuthenticated")]
public bool? IsAuthenticated { get; set; }
[CascadingParameter(Name = "NamedUser")]
public CascadingDemoUser? NamedUser { get; set; }
[CascadingParameter]
public CascadingDemoUser? TypedUser { get; set; }
private static string? FormatUser(CascadingDemoUser? user) => user is null ? null : $"{user.Name} [{user.Role}]";
}
```
`CascadingDemoUser.cs`:
```csharp
public sealed record CascadingDemoUser(string Name, string Role);
```
@(Title ?? "Child component with cascading parameters:")
Theme:
@(Theme ?? "null")
Notifications:
@(NotificationCount?.ToString() ?? "null")
Authenticated:
@(IsAuthenticated?.ToString() ?? "null")
User (named parameter):
@(FormatUser(NamedUser) ?? "null")
User (typed parameter):
@(FormatUser(TypedUser) ?? "null")
@(Title ?? "Child component with cascading parameters:")
Theme:
@(Theme ?? "null")
Notifications:
@(NotificationCount?.ToString() ?? "null")
Authenticated:
@(IsAuthenticated?.ToString() ?? "null")
User (named parameter):
@(FormatUser(NamedUser) ?? "null")
User (typed parameter):
@(FormatUser(TypedUser) ?? "null")
@(Title ?? "Child component with cascading parameters:")
Theme:
@(Theme ?? "null")
Notifications:
@(NotificationCount?.ToString() ?? "null")
Authenticated:
@(IsAuthenticated?.ToString() ?? "null")
User (named parameter):
@(FormatUser(NamedUser) ?? "null")
User (typed parameter):
@(FormatUser(TypedUser) ?? "null")
@(Title ?? "Child component with cascading parameters:")
Theme:
@(Theme ?? "null")
Notifications:
@(NotificationCount?.ToString() ?? "null")
Authenticated:
@(IsAuthenticated?.ToString() ?? "null")
User (named parameter):
@(FormatUser(NamedUser) ?? "null")
User (typed parameter):
@(FormatUser(TypedUser) ?? "null")
@Title
@(Count?.ToString() ?? "null")
@code {
[Parameter] public string? Title { get; set; }
[CascadingParameter]
public int? Count { get; set; }
}
```
\n**IsFixed**:
```razor
@(Title ?? "Child component with cascading parameters:")
Theme:
@(Theme ?? "null")
Notifications:
@(NotificationCount?.ToString() ?? "null")
Authenticated:
@(IsAuthenticated?.ToString() ?? "null")
User (named parameter):
@(FormatUser(NamedUser) ?? "null")
User (typed parameter):
@(FormatUser(TypedUser) ?? "null")
@(Title ?? "Child component with cascading parameters:")
Theme:
@(Theme ?? "null")
Notifications:
@(NotificationCount?.ToString() ?? "null")
Authenticated:
@(IsAuthenticated?.ToString() ?? "null")
User (named parameter):
@(FormatUser(NamedUser) ?? "null")
User (typed parameter):
@(FormatUser(TypedUser) ?? "null")
@(Title ?? "Child component with cascading parameters:")
Status:
@(Status?.Text ?? "null")
Progress:
@(Progress?.ToString() ?? "null")
@(Title ?? "Child component with cascading parameters:")
Status:
@(Status?.Text ?? "null")
Count:
@(Status?.Count.ToString() ?? "null")
Factory invocations so far: @lazyUserFactoryCalls
@(Title ?? "Child component with cascading parameters:")
Theme:
@(Theme ?? "null")
Notifications:
@(NotificationCount?.ToString() ?? "null")
Authenticated:
@(IsAuthenticated?.ToString() ?? "null")
User (named parameter):
@(FormatUser(NamedUser) ?? "null")
User (typed parameter):
@(FormatUser(TypedUser) ?? "null")
@(Title ?? "Child component with cascading parameters:")
Theme:
@(Theme ?? "null")
Notifications:
@(NotificationCount?.ToString() ?? "null")
Authenticated:
@(IsAuthenticated?.ToString() ?? "null")
User (named parameter):
@(FormatUser(NamedUser) ?? "null")
User (typed parameter):
@(FormatUser(TypedUser) ?? "null")