Diogo Neves
Diogo Neves

Reputation: 644

Parameter / CascadingParameter not updating object

I want to update a TodoItem in a child component TodoComponent passing a EventCallback.

In my TodoPage.razor.cs I've implemented an update method:

public async void UpdateTodo(TodoItem todoItem)
{
    await Http.PutAsJsonAsync("todo", todoItem);
    await RefreshTodos();
}

And then I'm trying to pass to the TodoComponent that is responsible to render the object

@foreach (var item in @TodoItems)
{
    <div class="row">
            <CascadingValue Value=@item Name="TodoItem">
                <TodoComponent UpdateCallback="@(async () => UpdateTodo(item))">
                </TodoComponent>
            </CascadingValue>
    </div>
}

TodoComponent.razor

@inherits ListOfTodos.Client.Components.TodoComponentBase
<div class="input-group col-12">
    <div class="input-group-prepend">
        <div class="input-group-text">
            <input type="checkbox" @bind="@TodoItem.IsDone" onclick="@UpdateCallback">
        </div>
    </div>

    <div class="col-8">
        <input type="text" class="form-control" @bind="@TodoItem.TaskName" oninput="@UpdateCallback" />
    </div>

    <div class="input-group-append">
        <input type="date" @bind="@TodoItem.DueDate" />
    </div>
</div>

Aditional info:

public async Task RefreshTodos()
{
    TodoItems = await Http.GetFromJsonAsync<List<TodoItem>>("todo");
    StateHasChanged();
}
public class TodoComponentBase : ComponentBase
{
    [CascadingParameter(Name = "TodoItem")]
    public TodoItem TodoItem { get; set; }

    [Parameter]
    public EventCallback<TodoItem> UpdateCallback { get; set; }

    protected override Task OnInitializedAsync()
    {
        return base.OnInitializedAsync();
    }

}

When I call the UpdateCallback though onchange or oninput the TodoItemcomes like if it wasn't updated at all, I don't know if the binding is working as I intend it to do.

How do I properly pass the object to the child component and have it update correctly?

This is the github repo: https://github.com/DgoNeves/Blazor.ListOfTodos It works in memory so you don't need any database to run it.

Edit: I don't exactly know why this happens but apparently the controller call is behing for some reason. Blazor Update Behind

Upvotes: 0

Views: 5382

Answers (3)

Zanyar Jalal
Zanyar Jalal

Reputation: 1864

StateHasChanged(); Only update current page with Childs, does not affect parent component.

For communication between child-parent component use must EventCallback

<CascadingValue Value="yourModel">
    <ChildComponent OnStateHasChanged="OnStateHasChanged" />
</CascadingValue>

ParentComponent

private void OnStateHasChanged()
{
    StateHasChanged();
}

Child Component

[Parameter]
public EventCallback OnStateHasChanged { get; set; }

private async Task OnUpdatedChild()
{
      await OnStateHasChanged.InvokeAsync();
}

Upvotes: 2

Diogo Neves
Diogo Neves

Reputation: 644

I've figured it out!

@bind="@TodoItem.TaskName" executes by default onChange

onInput apparently happens before the onChange so in the screenshot when I get 1 character behind it means that it did not update the TodoItem.TaskName because that's the next step.

If I really want to call the API on every keystroke I need to change it to:

@bind-value="@TodoItem.TaskName" @bind-value:event="oninput" onchange="@UpdateCallback"

This way I change the binding to happen onInputand I call the UpdateCallback onChange. Because the onInput happens first I get the updated value on onChange therefore passing the correct value.

Source from where I've found this: https://github.com/dotnet/aspnetcore/issues/15554 He had exactly the same error as I did

Upvotes: 0

agua from mars
agua from mars

Reputation: 17404

Ok, I just get your issue. It's your UpdateTodo method :
An async method must return a Task or a ValueTask. Update your code with :

public async Task UpdateTodo(TodoItem todoItem)
{
    await Http.PutAsJsonAsync("todo", todoItem);
    await RefreshTodos();
}

Upvotes: 0

Related Questions