I'm trying out .NET MAUI with Blazor, and running into issues when I extract some code from one component into a new component.
Given a list of a class which looks like this:
public class Expense
{
public DateTime Date { get; set; }
public decimal Amount { get; set; }
public string Note { get; set; }
public Expense(DateTime date, decimal amount, string note)
{
Date = date;
Amount = amount;
Note = note;
}
}
When I work such a list inside of a Blazor component, running inside of .NET MAUI, everything works fine when I have it all inside of one component, such as:
<button @onclick="AddExpense"><i ></i></button>
<table >
<thead>
<tr>
<th>Date</th>
<th>Amount</th>
<th>Note</th>
<th></th>
</tr>
</thead>
<tbody>
@for (var i = 0; i < expenses.Count; i )
{
int copy = i;
<tr>
<td>
<input type="date" min="2022-01-01" max="@currentDay"
@bind="@expenses[copy].Date"
@bind:format="yyyy-MM-dd" />
</td>
<td>
<input type="number" min="0" step="0.01" @bind="@expenses[copy].Amount" />
</td>
<td>
<input type="text" @bind="@(expenses[copy].Note)" />
</td>
<td>
<button @onclick="() => expenses.RemoveAt(copy)"><i ></i></button>
</td>
</tr>
}
</tbody>
</table>
@code {
region
private readonly string currentDay = $"{DateTime.Now.Year}-{DateTime.Now.Month}-{DateTime.Now.Day}";
private List<Expense> expenses = new List<Expense>()
{
new Expense(DateTime.Today, 100, "test")
};
private void AddExpense()
{
expenses.Add(new Expense(DateTime.Today, 0, ""));
}
However, I attempted to extract the table rows into their own ExpenseRow
component, which looks like this:
<tr>
<td>
<input type="date" min="@MinAttributeValue" max="@MaxAttributeValue"
@bind="@Date"
@bind:format="yyyy-MM-dd"/>
</td>
<td>
<input type="number" min="0" step="0.01" @bind="@Amount"/>
</td>
<td>
<input type="text" @bind="@Note"/>
</td>
<td>
<button @onclick="() => OnRemoveClick()"><i ></i></button>
</td>
</tr>
@code {
region:
private string MinAttributeValue => Min.ToString("yyyy-MM-dd");
private string MaxAttributeValue => Max?.ToString("yyyy-MM-dd");
[Parameter]
public DateTime Min { get; set; } = new DateTime(2022, 1, 1);
[Parameter]
public DateTime? Max { get; set; }
[Parameter]
public DateTime Date { get; set; }
[Parameter]
public EventCallback<DateTime> DateChanged { get; set; }
[Parameter]
public decimal Amount { get; set; }
[Parameter]
public EventCallback<decimal> AmountChanged { get; set; }
[Parameter]
public string Note { get; set; }
[Parameter]
public EventCallback<string> NoteChanged { get; set; }
[Parameter]
public Action OnRemoveClick { get; set; }
And I updated the original component to render this new component in the for
loop:
<table >
<thead>
<tr>
<th>Date</th>
<th>Amount</th>
<th>Note</th>
<th></th>
</tr>
</thead>
<tbody>
@for (var i = 0; i < expenses.Count; i )
{
int copy = i;
<ExpenseRow
@bind-Amount="expenses[copy].Amount"
@bind-Date="expenses[copy].Date"
@bind-Note="expenses[copy].Note"
Max="@DateTime.Today"
OnRemoveClick="() => { expenses.RemoveAt(copy); StateHasChanged(); }" />
}
</tbody>
</table>
This change apparently breaks the binding back to the objects in the list:
- The properties on each item in the list seem to not be updated. This is evidenced by the values being reverted once I tab away from the field, or when I add a new item to the list via the Add button.
- The 'delete' button does not remove a row from the table after it is clicked.
- I was able to fix this behavior by adding in the call to
StateHasChanged()
.
- I was able to fix this behavior by adding in the call to
I also attempted to invoke StateHasChanged()
in this way, based on