Search code examples
c#callbacktimepickermudblazor

Parent Component not showing time picked in custom MudTimePicker component


I wanted to create a custom TimePickerComponent and use it everywhere in my app where i use a MudTimePicker. This, because that way I can easily configure AutoClose=false everywhere, and in the future maybe also other custom options.

The previous snippet is solved by the comments below. However, it seems that the surrounding components have influence on it as well. The TimePickerComponent is inside a MudDataGrid.

<MudDataGrid T=Item Items="_items" 
    Striped=true Hover=true Dense="true" Square="true" Elevation="0" Bordered="true" 
    ReadOnly="false"
    EditMode="DataGridEditMode.Form"
    EditTrigger="DataGridEditTrigger.Manual"
    StartedEditingItem="StartedEditingItem"
    CommittedItemChanges="CommittedItemChanges">
    
    <Columns>
        <PropertyColumn Property="item => item.DeleteDate" Title="Delete Date" Sortable="true" Filterable="true">
            <EditTemplate>
                <MudDatePicker @bind-Date="@deletedDate" Label="Delete Date" />
                <MudTimePicker @bind-Time="@deletedTime" Label="Delete Date" />

                <TimePickerComponent @bind-TimeValue="@deletedTime" Label="Delete Time From Custom Component" />
            </EditTemplate>
        </PropertyColumn>

        <TemplateColumn>
            <CellTemplate>
                <MudIconButton Size="@Size.Small" Icon="@Icons.Material.Outlined.Edit" OnClick="@context.Actions.StartEditingItemAsync" />
            </CellTemplate>
        </TemplateColumn>
    </Columns>

</MudDataGrid>

I am now using the new TimePickerComponent as provided by @RBee .

<MudTimePicker @ref=_picker Time=@TimeValue TimeChanged="HandleTimeChange" Label=@Label AutoClose="false">
    <PickerActions>
        <MudButton OnClick="@(() => _picker.CloseAsync(false))">Cancel</MudButton>
        <MudButton Color="Color.Primary" OnClick="@(() => _picker.CloseAsync())">Ok</MudButton>
    </PickerActions>
</MudTimePicker>

@code {
    [Parameter]
    public TimeSpan? TimeValue { get; set; }
    [Parameter]
    public EventCallback<TimeSpan?> TimeValueChanged { get; set; }

    [Parameter]
    public string Label { get; set; }

    public MudTimePicker _picker;

    public async void HandleTimeChange(TimeSpan? newTime){
        await TimeValueChanged.InvokeAsync(newTime);
    }
}

The HandleTimeChange gets called twice, once with the correct value, and once with the value null. This means, that it still does not work unfortunately. See this snippet: try.mudblazor.com/snippet/cuwoaCaKTmCeSBDO


Original post/code:

<MudDatePicker @bind-Date="@deletedDate" Label="Delete Date" />
<MudTimePicker @bind-Time="@deletedTime" Label="Delete Time"/>

<TimePickerComponent @bind-TimeValue="@deletedTime" Label="Delete Time From Custom Component"/>

@code{
    public DateTime? deletedDate { get; set; }
    public TimeSpan? deletedTime { get; set; }
}

However, the parent component seems to be unable to show the time I've picked in my TimePickerComponent component. I created a MudBlazor snippet here: https://try.mudblazor.com/snippet/cYmIuiYpzoGHegIi . When I use the MudTimePicker to pick a time, both the MudTimePicker and the TimePickerComponent show the selected time. Not vice versa.

I have tried MudBlazor 7.4 and 7.5 packages, they both show the same behavior.

I expect something is wrong with the way I have configured the TimeValueChanged EventCallBack.

I have also tried defining an OnOkButtonClicked function where I invoke the TimeValueChanged async. However, this also does not seem to be working.

    private void OnOkButtonClicked()
    {
        TimeValueChanged.InvokeAsync();
        _picker.CloseAsync();
    }

Solution

  • You have to use the EventCallback to notify the parent of a change. Directly @bind'ing across multiple components is not a good idea.

    In the example below, we use Time and TimeChanged instead of @bind-Time, Then in the handler method HandleTimeChange we don't assign the new value to TimeValue as that is the parent components responsibility, we only invoke the TimeValueChanged EventCallback thereby notifying the parent of a change.

    <MudTimePicker @ref=_picker Time=@TimeValue TimeChanged="HandleTimeChange" Label=@Label AutoClose="false">
        <PickerActions>
            <MudButton OnClick="@(() => _picker.CloseAsync(false))">Cancel</MudButton>
            <MudButton Color="Color.Primary" OnClick="@(() => _picker.CloseAsync())">Ok</MudButton>
        </PickerActions>
    </MudTimePicker>
    
    @code {
        [Parameter]
        public TimeSpan? TimeValue { get; set; }
        [Parameter]
        public EventCallback<TimeSpan?> TimeValueChanged { get; set; }
    
        [Parameter]
        public string Label { get; set; }
    
        public MudTimePicker _picker;
    
        public void Dispose()
        {
            _picker.DisposeAsync();
        }
        private async void HandleTimeChange(TimeSpan? newTime){
            await TimeValueChanged.InvokeAsync(newTime);
        }
    }
    

    MudBlazor Snippet


    Edit: Updated to the code change in question.

    Seems you also need to assign the Time value in the custom component. Not entirely sure why, although the docs does note

    Note: Always use the two-way binding @bind-Time to bind to a field of type TimeSpan?

    [Parameter]
    public TimeSpan? TimeValue { get; set; }
    [Parameter]
    public EventCallback<TimeSpan?> TimeValueChanged { get; set; }
    
    private async void HandleTimeChange(TimeSpan? newTime){
        TimeValue=newTime; // also assigned here
        await TimeValueChanged.InvokeAsync(newTime);
    }
    

    MudBlazor Snippet