Megabit / Blazorise

Blazorise is a component library built on top of Blazor with support for CSS frameworks like Bootstrap, Tailwind, Bulma, AntDesign, and Material.
https://blazorise.com/
Other
3.26k stars 528 forks source link

[Bug]: Button in dataGrid is not clickable for a few second on first load(flickering) #5638

Open danik121 opened 1 month ago

danik121 commented 1 month ago

Blazorise Version

1.6

What Blazorise provider are you running on?

FluentUI2

Link to minimal reproduction or a simple code snippet

example in video https://github.com/user-attachments/assets/67778a34-84d3-4e60-b027-4c43043a109f

I use await dataGridRef.LoadState(state); when i remove it it works fine

Steps to reproduce

@page "/tests/datagrid/state"
<Row>
  <Column>
    <Card Margin="Margin.Is4.OnY">
      <CardHeader>
        <CardTitle>Datagrid: State Management</CardTitle>
      </CardHeader>
      <CardBody>
        <Paragraph>
          Our DataGrid allows you to save and load state.
          You can use the <code>LoadState</code> and <code>GetState</code> methods to load and get the DataGrid state.
        </Paragraph>
        <Paragraph>
          In the following example,
          <UnorderedList>
            <UnorderedListItem>we are using the <code>LoadState</code> method to load the DataGrid state from the LocalStorage if available.</UnorderedListItem>
            <UnorderedListItem>We are using the <code>GetState</code> method to save the DataGrid state to the LocalStorage in order to load at a later date.</UnorderedListItem>
            <UnorderedListItem>The page checks the LocalStorage on first render and loads the saved state if available.</UnorderedListItem>
          </UnorderedList>
        </Paragraph>
        <Paragraph>
          <Button Color="Color.Primary" Clicked="LoadState">Load State</Button>
          <Button Color="Color.Success" Clicked="SaveState">Save State</Button>
          <Button Color="Color.Light" Clicked="ResetState">Reset State</Button>
        </Paragraph>
      </CardBody>
      <CardBody>
        <DataGrid FixedColumns @ref="dataGridRef"
                  ColumnDisplayingChanged="@ColumnDisplayChanged"
                  TItem="Employee"
                  Data="@inMemoryData"
                  Editable="true"
                  ShowPager
                  PageSize="15"
                  Filterable="true"
                  Sortable="true"
                  Responsive="true"
                  FixedHeader="true"
                  FixedHeaderDataGridMaxHeight="calc(100vh - 175px)"
                  FixedHeaderDataGridHeight="calc(100vh - 175px)"
                  PagerPosition="DataGridPagerPosition.Top">
          <DataGridColumns>
            <DataGridColumn TextAlignment="TextAlignment.Center" TItem="Employee" Field="@nameof( Employee.Id )" Caption="#" Width="60px" />
            <DataGridColumn TItem="Employee" Field="@nameof( Employee.FirstName )" Caption="First Name">
            </DataGridColumn>
            <DataGridColumn TItem="Employee" Field="@nameof( Employee.LastName )" Caption="Last Name" />
            <DataGridColumn TItem="Employee" Field="@nameof( Employee.Email )" Caption="Email" />
            <DataGridColumn TItem="Employee" Field="@nameof( Employee.City )" Caption="City">
              <DisplayTemplate>
                <Button Clicked="() => LoadState()">
                  @context.City
                </Button>
              </DisplayTemplate>
            </DataGridColumn>
            <DataGridColumn TItem="Employee" Field="@nameof( Employee.Zip )" Caption="Zip">
              <DisplayTemplate>
                <Button Clicked="() => NavigateTo()">
                  @context.Zip
                </Button>
              </DisplayTemplate>
            </DataGridColumn>
            <DataGridDateColumn TItem="Employee" Field="@nameof( Employee.DateOfBirth )" DisplayFormat="{0:dd.MM.yyyy}" Caption="Date Of Birth" Editable />
            <DataGridNumericColumn TItem="Employee" Field="@nameof( Employee.Childrens )" Caption="Childrens" ReverseSorting="true" Editable Filterable="false" />
            <DataGridSelectColumn TItem="Employee" Field="@nameof( Employee.Gender )" Caption="Gender" Editable Data="EmployeeData.Genders" ValueField="(x) => ((Gender)x).Code" TextField="(x) => ((Gender)x).Description" />
            <DataGridColumn TItem="Employee" Field="@nameof( Employee.Salary )" Caption="Salary" Editable Width="140px" DisplayFormat="{0:C}" DisplayFormatProvider="@System.Globalization.CultureInfo.GetCultureInfo("fr-FR")" TextAlignment="TextAlignment.End">
            </DataGridColumn>
            <DataGridCheckColumn TItem="Employee" Field="@nameof(Employee.IsActive)" Caption="Active" Editable Filterable="false">
              <DisplayTemplate>
                <Check TValue="bool" Checked="context.IsActive" Disabled ReadOnly />
              </DisplayTemplate>
            </DataGridCheckColumn>
          </DataGridColumns>
        </DataGrid>
      </CardBody>
    </Card>
  </Column>
</Row>

@code {
  [Inject] Blazored.LocalStorage.ILocalStorageService LocalStorage { get; set; }
  [Inject] EmployeeData EmployeeData { get; set; }
  [Inject] public NavigationManager NavigationManager { get; set; }
  private const string STORAGE_KEY = "__DATAGRID_STATE__";
  private DataGrid<Employee> dataGridRef;
  private IEnumerable<Employee> inMemoryData => EmployeeData.GetDataAsync().Result.Take(25);

  protected override async Task OnInitializedAsync()
  {
    // inMemoryData =
    await base.OnInitializedAsync();
  }

  protected async override Task OnAfterRenderAsync(bool firstRender)
  {
    if (firstRender)
    {
      await LoadState();
    }
    await base.OnAfterRenderAsync(firstRender);
  }
  public async Task ColumnDisplayChanged(ColumnDisplayChangedEventArgs<Employee> args)
  {
    var state = await dataGridRef.GetState();
    await LocalStorage.SetItemAsync(STORAGE_KEY, state);
  }

  private async Task ResetState()
  {
    await LocalStorage.RemoveItemAsync(STORAGE_KEY);
    var state = new DataGridState<Employee>()
      {
        CurrentPage = 1,
        PageSize = 10,
      };
    await dataGridRef.LoadState(state);
  }

  private async Task LoadState()
  {
    var stateFromLocalStorage = await LocalStorage.GetItemAsync<DataGridState<Employee>>(STORAGE_KEY);

    if (stateFromLocalStorage is not null)
    {
      //It is of note that we must make sure the reference is contained in the DataGrid Data collection.
      if (stateFromLocalStorage.SelectedRow is not null)
      {
        stateFromLocalStorage.SelectedRow = inMemoryData.FirstOrDefault(x => x.Id == stateFromLocalStorage.SelectedRow.Id);
      }
      if (stateFromLocalStorage.EditItem is not null)
      {
        stateFromLocalStorage.EditItem = inMemoryData.FirstOrDefault(x => x.Id == stateFromLocalStorage.EditItem.Id);
      }
      await dataGridRef.LoadState(stateFromLocalStorage);
      return;
    }
  }

  public void NavigateTo()
  {
    NavigationManager.NavigateTo("/tests/datagrid/sort/single");
  }

  private async Task SaveState()
  {
    var state = await dataGridRef.GetState();
    await LocalStorage.SetItemAsync(STORAGE_KEY, state);
  }
}

What is expected?

click on button

What is actually happening?

button flickering and i have to wait when it stops then can i click on button

What browsers do you see the problem on?

No response

Any additional comments?

No response

stsrki commented 1 month ago

Do you maybe have some long-running process while loading the page that might freeze the UI thread?

Also, a reproducible code would greatly help. Thanks.

github-actions[bot] commented 1 month ago

Hello @danik121, thank you for your submission. The issue was labeled "Status: Repro Missing", as you have not provided a way to reproduce the issue quickly. Most problems already solve themselves when isolated, but we would like you to provide us with a reproducible code to make it easier to investigate a possible bug.

danik121 commented 1 month ago

@stsrki added reproducible code and I found what the problem is await dataGridRef.LoadState(state); creates this problem

David-Moreira commented 1 month ago

Hello @danik121 the code is a bit incomplete, would it be possible to add the logic part? Thanks!

danik121 commented 1 month ago

Hello @danik121 the code is a bit incomplete, would it be possible to add the logic part? Thanks!

Updated

illiniguy commented 1 month ago

Check what render mode and/or if pre-rendering is used/enabled. The latter can cause the HTML to be displayed to the user but events will not work right away.

David-Moreira commented 1 month ago

Hello @danik121

There's still code missing to promptly run your example.

Anyway we do have examples on our docs and on our demo of the feature and both seem to be working fine. Are you able to reproduce your issue in one of our examples? https://blazorise.com/docs/extensions/datagrid/features/state-management https://bootstrapdemo.blazorise.com/tests/datagrid/state

Otherwise, perhaps @illiniguy is on to something. Prerender for instance makes it so the page isn't interactive right away, and can cause flickering if components need to re-render.

If you still require help with this matter, we ask you that you provide an actual github repo so we can actually troubleshoot your issue.

danik121 commented 1 month ago

Hello @danik121

There's still code missing to promptly run your example.

Anyway we do have examples on our docs and on our demo of the feature and both seem to be working fine. Are you able to reproduce your issue in one of our examples? https://blazorise.com/docs/extensions/datagrid/features/state-management https://bootstrapdemo.blazorise.com/tests/datagrid/state

Otherwise, perhaps @illiniguy is on to something. Prerender for instance makes it so the page isn't interactive right away, and can cause flickering if components need to re-render.

If you still require help with this matter, we ask you that you provide an actual github repo so we can actually troubleshoot your issue.

Example updated with some changes in the demo code I was able to reproduce the problem

David-Moreira commented 1 month ago

Hello @danik121

What changes did you do?

In your example I still see alot unusable types... I still don't think I can't just copy the code into my own project and run it right away. We can't reliable run the reproduceable under the same conditions as you if we have to be making modifications to the code.

The idea is to get a stripped down version without custom code reproducing the issue. Sometimes when you isolate problems in this manner, problems already solve themselves. But as you said, if you were able to reproduce in the demo code, just kindly give us the exact steps for us to properly reproduce without having to do guess work.

danik121 commented 1 month ago

Hello @danik121

What changes did you do?

In your example I still see alot unusable types... I still don't think I can't just copy the code into my own project and run it right away. We can't reliable run the reproduceable under the same conditions as you if we have to be making modifications to the code.

The idea is to get a stripped down version without custom code reproducing the issue. Sometimes when you isolate problems in this manner, problems already solve themselves. But as you said, if you were able to reproduce in the demo code, just kindly give us the exact steps for us to properly reproduce without having to do guess work.

sorry my mistake i copied wrong code. i corrected it

David-Moreira commented 1 month ago

Hello @danik121 What changes did you do? In your example I still see alot unusable types... I still don't think I can't just copy the code into my own project and run it right away. We can't reliable run the reproduceable under the same conditions as you if we have to be making modifications to the code. The idea is to get a stripped down version without custom code reproducing the issue. Sometimes when you isolate problems in this manner, problems already solve themselves. But as you said, if you were able to reproduce in the demo code, just kindly give us the exact steps for us to properly reproduce without having to do guess work.

sorry my mistake i copied wrong code. i corrected it

Appreciated, we'll let you know once we have feedback.

David-Moreira commented 1 month ago

@danik121 Unfortunately I cannot reproduce your issue:

Can you take a look at the following gif and let me know if I'm doing something wrong? I'm running Fluent UI WASM. chrome-capture-2024-8-3 (1)

github-actions[bot] commented 1 month ago

Hello @danik121, thank you for your submission. The issue was labeled "Status: Repro Missing", as you have not provided a way to reproduce the issue quickly. Most problems already solve themselves when isolated, but we would like you to provide us with a reproducible code to make it easier to investigate a possible bug.

danik121 commented 1 month ago

@danik121 Unfortunately I cannot reproduce your issue:

Can you take

a look at the following gif and let me know if I'm doing something wrong? I'm running Fluent UI WASM. !

I use blazor server chrome-capture-2024-8-5sss

David-Moreira commented 1 month ago

Hello @danik121 I still cannot reproduce...

That's why a repro is usually the best way because we can be certain I'm running everything the same as you... If there's something slightly different that can be enough for not being able to reproduce the issue... Anyway here's my steps:

danik121 commented 1 month ago

@David-Moreira can you try Fluent UI and blazor server?

David-Moreira commented 1 month ago

It still works on my end, apparently as expected?

Respectfully, we can't be doing guess work. We are loosing too much time back and forth as you haven't provided a proper repro as requested. We can't be sure whether it's us that are not reproducing correctly or it's an environment issue on your end.

Please provide the repro so at least we can eliminate the possibility of us not reproducing correctly. Here's an updated gif: bootstrap-demo_2024-8-6

danik121 commented 1 month ago

@David-Moreira when local storage empty it works fine as you expected chrome-capture-2024-8-7 When is save save local storage and then try to load state from local storage. I hope u can reproduce that. everyone in our team can reproduce it chrome-capture-2024-8-7 (1)

David-Moreira commented 1 month ago

Hello @danik121 Finally!! So we were indeed not testing under the same conditions, thanks for providing proper instructions.

So the problem is that the grid is being re-rendered since you are loading your saved state. The reason why you put both buttons so close to each other, the LoadState and the navigation button, is because the grid is in this state for like a second. So it's not as bad a problem I would say, also I don't see any noticeable flickering which is great.

Anyway in order to properly fix this, you can set the grid to loading manually.

  1. Add a LoadingTemplate
                     <LoadingTemplate>
                        Loading...
                    </LoadingTemplate>
  2. Set the grid to loading like so :

    private async Task LoadState()
    {
    
    var stateFromLocalStorage = await LocalStorage.GetItemAsync<DataGridState<Employee>>( STORAGE_KEY );
    
    if ( stateFromLocalStorage is not null )
    {
        dataGridRef.SetLoading(true );
    
        //It is of note that we must make sure the reference is contained in the DataGrid Data collection.
        if ( stateFromLocalStorage.SelectedRow is not null )
        {
            stateFromLocalStorage.SelectedRow = inMemoryData.FirstOrDefault( x => x.Id == stateFromLocalStorage.SelectedRow.Id );
        }
        if ( stateFromLocalStorage.EditItem is not null )
        {
            stateFromLocalStorage.EditItem = inMemoryData.FirstOrDefault( x => x.Id == stateFromLocalStorage.EditItem.Id );
        }
        await dataGridRef.LoadState( stateFromLocalStorage );
        dataGridRef.SetLoading( false );
        return;
    }
    }

This will make sure that by the time the grid gets back it will be interactable. If by any change the loading of the State takes longer, let's say 3seconds, then the grid will be in a loading state for that time. bootstrap-demo_2024-8-7


@stsrki What are your thoughts? I feel like we shouldn't change how to behaves right now, but maybe we could introduce an optional parameter forceLoading or setLoading?

stsrki commented 1 month ago

@stsrki What are your thoughts? I feel like we shouldn't change how to behaves right now, but maybe we could introduce an optional parameter forceLoading or setLoading?

Not sure if I understand. Can you explain why would we need it? We already have dataGridRef.SetLoading( true );.

David-Moreira commented 1 month ago

@stsrki What are your thoughts? I feel like we shouldn't change how to behaves right now, but maybe we could introduce an optional parameter forceLoading or setLoading?

Not sure if I understand. Can you explain why would we need it? We already have dataGridRef.SetLoading( true );.

It's just a matter of allowing an operation that can make the DataGrid unusable for 1s or more have a quick way to make it set to loading. Just a shortcut / nice to have let's say. It's also arguable whether it should be something it does by default, since maybe the grid shouldn't be interactable during this time.