New to Telerik UI for Blazor? Download free 30-day trial

Grid State

The Grid lets you read, save, load, and change its state through code. The state includes the Grid features that are controlled by the user, such as the current sorting, page number, applied grouping, column widths, and many others.

This article describes:

Information in the Grid State

The Grid state is a generic class GridState<TItem>. The type depends on the type of the Grid model. The GridState<TItem> object exposes the following properties:

Property Type Description
CollapsedGroups ICollection<int> The indexes of all collapsed groups when LoadGroupsOnDemand="false", i.e. when not loading groups on demand.
ColumnStates ICollection<GridColumnState> Information about each column's reorder index, width, visibility, locked state, Id parameter value and Field. The column order in the collection matches the column order in the Grid declaration. On the other hand, the Index property matches the current column's position in the UI. Id and Field are always null after deserialization, because these properties have no public setters.
EditField string The currently edited data item property in Incell edit mode.
EditItem TItem* The currently edited data item in any edit mode.
ExpandedItems ICollection<TItem> The expanded data items, when using <DetailTemplate> (hierarchy).
FilterDescriptors ICollection<IFilterDescriptor> All filtering criteria, except the ones that relate to theGridSearchBox.
GroupDescriptors ICollection<GroupDescriptor> Information about currently applied grouping.
InsertedItem TItem* The data item that is being added in Inline or Popup edit mode. Not applicable for Incell editing.
OriginalEditItem TItem* The original copy of the data item that is currently in edit mode. This GridState property holds the unmodified data item values.
Page int? The current page index. Some user actions reset the page index to 1, such as filtering or changing the page size.
SearchFilter IFilterDescriptor The CompositeFilterDescriptor that holds the filter descriptors for the GridSearchBox.
SelectedItems ICollection<TItem> The currently selected data item(s).
Skip int? The number of scrolled data items when using virtual row scrolling. In other words, this is the number of rows above the currently visible ones.
SortDescriptors ICollection<SortDescriptor> The currently applied sorts.
TableWidth string The sum of all visible column widths. This property changes together with ColumnStates. The OnStateChanged event does not fire separately for it.

* TItem is the Grid model type.

Events

The Grid features two events, which are related to its state.

OnStateInit

The OnStateInit event fires when the Grid is initializing. Use this event to:

  • Define initial state, for example default initial sorting;
  • Load and apply state that was previously saved in a database or in localStorage.

The generic event argument is of type GridStateEventArgs<TItem> and has a GridState property. See Information in the Grid State for details.

If you change the column order or number of columns in the Grid declaration, this can break state restore. In such cases, either ignore the stored column state, or implement custom logic to restore only the columns that still exist in the Grid.

To set the initial visibility of columns, better use the Visible parameter, rather than conditional markup for the whole column. The Visible parameter values will be present in the Grid state and the columns collection count will remain the same. This makes it easier to reconcile changes.

The example below shows how to apply initial sorting, filtering and grouping.

Using Grid OnStateInit

@using Telerik.DataSource

<TelerikGrid Data="@GridData"
             Pageable="true"
             PageSize="5"
             Sortable="true"
             FilterMode="@GridFilterMode.FilterMenu"
             Groupable="true"
             OnStateInit="@( (GridStateEventArgs<Product> args) => OnGridStateInit(args) )">
    <GridColumns>
        <GridColumn Field="@nameof(Product.Name)" />
        <GridColumn Field="@nameof(Product.Category)" />
        <GridColumn Field="@nameof(Product.Stock)" />
        <GridColumn Field="@nameof(Product.Discontinued)" />
    </GridColumns>
</TelerikGrid>

@code {
    private List<Product> GridData { get; set; }

    private async Task OnGridStateInit(GridStateEventArgs<Product> args)
    {
        // Sort by Stock
        args.GridState.SortDescriptors.Add(new SortDescriptor()
        {
            Member = nameof(Product.Stock),
            SortDirection = ListSortDirection.Descending
        });

        // Filter Discontinued products
        var discontinuedColumnFilter = new CompositeFilterDescriptor()
        {
            FilterDescriptors = new FilterDescriptorCollection() {
                 new FilterDescriptor()
                 {
                    Member = nameof(Product.Discontinued),
                    MemberType = typeof(bool),
                    Operator = FilterOperator.IsEqualTo,
                    Value = false
                 }
             }
        };
        args.GridState.FilterDescriptors.Add(discontinuedColumnFilter);

        // Group by Category
        args.GridState.GroupDescriptors.Add(new GroupDescriptor()
        {
            Member = nameof(Product.Category),
            MemberType = typeof(string)
        });
    }

    protected override void OnInitialized()
    {
        GridData = new List<Product>();
        var rnd = new Random();

        for (int i = 1; i <= 12; i++)
        {
            GridData.Add(new Product()
            {
                Id = i,
                Name = $"Product {i}",
                Category = $"Category {i % 2 + 1}",
                Stock = rnd.Next(0, 100),
                Discontinued = i % 3 == 0
            });
        }
    }

    public class Product
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string Category { get; set; }
        public int Stock { get; set; }
        public bool Discontinued { get; set; }
    }
}

OnStateChanged

OnStateChanged fires when the user performs an action that changes the value of a property in the Grid state. The event argument is of type GridStateEventArgs<TItem> and exposes these properties:

Property Type Description
PropertyName string Information about what changed in the Grid state. The possible values match the property names of the GridState object. The possible values for the PropertyName are SortDescriptors, FilterDescriptors, SearchFilter, GroupDescriptors, Page, Skip, CollapsedGroups, ColumnStates, ExpandedItems, InsertedItem, OriginalEditItem, EditItem.
GridState GridState<TItem> The current (up-to-date) Grid state object.

Here is some additional information about certain PropertyName values:

  • EditItem is used when the user starts editing an existing item.
  • InsertedItem signifies the user adding a new item in inline or popup edit mode. It's not applicable for Incell editing.
  • OriginalEditItem is used when the user exits edit or insert mode via save or cancel.
  • ColumnStates is used for several column actions such as hiding, showing, locking, reordering and resizing.

Some user actions will trigger two OnStateChanged events with a different PropertyName each time. These include filtering, searching and grouping. For example, filtering resets the current page to 1. First, the event will fire with PropertyName equal to "FilterDescriptors", and then PropertyName will be "Page". However, the GridState property of the event argument will provide correct information about the overall Grid state in both event handler executions.

We recommend using an async Task handler for the OnStateChanged event, in order to reduce re-rendering and avoid blocking UI updates if the handler will wait for a service to save the Grid state somewhere.

To observe the changes in the Grid state more easily, copy and run the following example in a local app and at full screen.

Using Grid OnStateChanged

@using System.Text.Json

<div id="demo-container">
    <TelerikGrid Data="@GridData"
                 EditMode="@GridEditMode.Inline"
                 FilterMode="@GridFilterMode.FilterMenu"
                 Groupable="true"
                 Pageable="true"
                 @bind-PageSize="@GridPageSize"
                 Reorderable="true"
                 Resizable="true"
                 @bind-SelectedItems="@GridSelectedItems"
                 SelectionMode="@GridSelectionMode.Multiple"
                 ShowColumnMenu="true"
                 Sortable="true"
                 OnCreate="@OnGridCreate"
                 OnUpdate="@OnGridUpdate"
                 OnStateChanged="@( (GridStateEventArgs<Product> args) => OnGridStateChanged(args) )">
        <GridSettings>
            <GridPagerSettings PageSizes="@( new List<int?>() { null, 5, 10 } )" />
        </GridSettings>
        <GridToolBarTemplate>
            <GridCommandButton Command="Add">Add</GridCommandButton>
            <GridSearchBox />
        </GridToolBarTemplate>
        <GridColumns>
            <GridCheckboxColumn SelectAll="true" />
            <GridColumn Field="@nameof(Product.Name)" />
            <GridColumn Field="@nameof(Product.Category)" />
            <GridColumn Field="@nameof(Product.Stock)" />
            <GridColumn Field="@nameof(Product.Discontinued)" />
            <GridCommandColumn>
                <GridCommandButton Command="Edit">Edit</GridCommandButton>
                <GridCommandButton Command="Save" ShowInEdit="true">Save</GridCommandButton>
                <GridCommandButton Command="Cancel" ShowInEdit="true">Cancel</GridCommandButton>
            </GridCommandColumn>
        </GridColumns>
        <DetailTemplate>
            Detail Template for product <strong>@context.Name</strong>.
        </DetailTemplate>
    </TelerikGrid>

    <div id="console">
        <code class="@GridStateChangedPropertyClass">OnStateChanged</code> count:
        @OnStateChangedCount
        <TelerikButton OnClick="@( () => OnStateChangedCount = 0 )">Reset</TelerikButton>
        <br /><br />
        Last <code>OnStateChanged</code> event:
        <br />
        <strong class="@GridStateChangedPropertyClass">PropertyName</strong>:
        <code>&quot;@GridStateChangedProperty&quot;</code>
        <br />
        <strong>GridState</strong>:
        <pre>
        @( new MarkupString(GridStateString) )
        </pre>
    </div>
</div>

<style>
    .first-of-two {
        color: #f00;
    }

    .latest-changed-property {
        color: #00f;
    }

    @@media (min-width: 800px) {
        #demo-container {
            display: flex;
            align-items: flex-start;
            gap: 1em;
        }

            #demo-container > .k-grid {
                flex: 2 2 800px;
            }

        #console {
            height: 90vh;
            overflow: auto;
            flex: 1 0 300px;
            border: 1px solid rgba(128, 128, 128, .3);
            padding: 1em;
        }
    }
</style>

@code {
    private List<Product> GridData { get; set; } = new List<Product>();

    private int GridPageSize { get; set; } = 5;

    private IEnumerable<Product> GridSelectedItems { get; set; } = new List<Product>();

    private int OnStateChangedCount { get; set; }

    private string GridStateChangedProperty { get; set; } = string.Empty;
    private string GridStateChangedPropertyClass { get; set; } = string.Empty;

    private string GridStateString { get; set; } = string.Empty;

    private bool _doubleStateChanged { get; set; }

    private List<string> _operationsWithMultipleStateChanged = new List<string>() {
        "FilterDescriptors",
        "GroupDescriptors",
        "SearchFilter"
    };

    private async Task OnGridStateChanged(GridStateEventArgs<Product> args)
    {
        if (_doubleStateChanged)
        {
            _doubleStateChanged = false;
            await Task.Delay(1500);
            GridStateChangedPropertyClass = string.Empty;
        }

        ++OnStateChangedCount;

        GridStateChangedProperty = args.PropertyName;

        // serialize the GridState and highlight the changed property
        GridStateString = JsonSerializer.Serialize(args.GridState, new JsonSerializerOptions() { WriteIndented = true })
            .Replace($"\"{GridStateChangedProperty}\"", $"\"<strong class='latest-changed-property'>{GridStateChangedProperty}</strong>\"");

        // highlight first GridStateChangedProperty during filtering, grouping and search
        if (_operationsWithMultipleStateChanged.Contains(GridStateChangedProperty))
        {
            _doubleStateChanged = true;
            GridStateChangedPropertyClass = "first-of-two";
        }
    }

    private void OnGridUpdate(GridCommandEventArgs args)
    {
        var updatedItem = (Product)args.Item;
        var originalItemIndex = GridData.FindIndex(x => x.Id == updatedItem.Id);

        GridData[originalItemIndex] = updatedItem;
    }

    private void OnGridCreate(GridCommandEventArgs args)
    {
        var createdItem = (Product)args.Item;

        GridData.Insert(0, createdItem);
    }

    protected override void OnInitialized()
    {
        var rnd = new Random();

        for (int i = 1; i <= 12; i++)
        {
            GridData.Add(new Product()
            {
                Id = i,
                Name = $"Product {i}",
                Category = $"Category {i % 4 + 1}",
                Stock = rnd.Next(0, 100),
                Discontinued = i % 3 == 0
            });
        }
    }

    public class Product
    {
        public int Id { get; set; }
        public string Name { get; set; } = string.Empty;
        public string Category { get; set; } = string.Empty;
        public int Stock { get; set; }
        public bool Discontinued { get; set; }
    }
}

Methods

The GetState and SetStateAsync methods of the Grid instance let you get and set the current Grid state on demand at any time after OnStateInit.

If you want to make changes to the current Grid state:

  1. First, get the current state with the GetState method.
  2. Apply the desired modifications to the obtained GridState object.
  3. Set the modified state object via the SetStateAsync method.

Do not use GetState() in the OnStateInit or OnStateChanged events. Do not use SetStateAsync() in OnStateInit. Instead, get or set the GridState property of the event argument.

Avoid calling SetStateAsync in the Grid CRUD methods (such as OnRead, OnUpdate, OnEdit, OnCreate, OnCancel). Doing so may lead to unexpected results because the Grid has more logic to execute after these events. Setting the Grid state fires OnRead, so calling SetStateAsync() in this handler can lead to an endless loop.

To reset the Grid state to its initial markup configuration, call SetStateAsync(null).

To reset the Grid state to a completely new configuration, create a new GridState<T>() and apply the settings there. Then pass the state object to SetStateAsync().

SetStateAsync Examples

The tabs below show how to set the Grid state and control filtering, sorting and other Grid features.

If you want to set an initial state to the Grid, use a similar snippet, but in the OnStateInit event

@using Telerik.DataSource

<TelerikGrid @ref="@GridRef"
             Data="@GridData"
             Pageable="true"
             Sortable="true"
             SortMode="@SortMode.Multiple"
             Height="400px">
    <GridToolBarTemplate>
        <TelerikButton ThemeColor="@ThemeConstants.Button.ThemeColor.Primary"
                       OnClick="@SetGridSort">Sort Grid by HireDate</TelerikButton>
        <label>
            <TelerikCheckBox @bind-Value="@ShouldResetSortState" />
            Reset Existing Sort State
        </label>
    </GridToolBarTemplate>
    <GridColumns>
        <GridColumn Field="@(nameof(Employee.Name))" Title="Employee Name" />
        <GridColumn Field="@(nameof(Employee.Team))" Title="Team" />
        <GridColumn Field="@(nameof(Employee.HireDate))" Title="Hire Date" DisplayFormat="{0:d}" />
        <GridColumn Field="@(nameof(Employee.IsOnLeave))" Title="Is On Leave" />
    </GridColumns>
</TelerikGrid>

@code {
    private TelerikGrid<Employee>? GridRef { get; set; }

    private List<Employee> GridData { get; set; } = new();

    private bool ShouldResetSortState { get; set; } = true;

    private async Task SetGridSort()
    {
        if (GridRef != null)
        {
            var gridState = GridRef.GetState();

            if (ShouldResetSortState)
            {
                // Remove any existing sorts.
                gridState.SortDescriptors.Clear();
            }

            SortDescriptor? hireDateSortDescriptor = gridState.SortDescriptors
                .Where(x => x.Member == nameof(Employee.HireDate)).FirstOrDefault();

            if (hireDateSortDescriptor != null)
            {
                // Update the existing HireDate sort if it exists.
                hireDateSortDescriptor.SortDirection = ListSortDirection.Descending;
            }
            else
            {
                // Add a new sort descriptor.
                // In multi-column sorting scenarios
                // you can also insert the new SortDescriptor
                // before the existing ones to control the sort priority.
                gridState.SortDescriptors.Add(new SortDescriptor()
                {
                    Member = nameof(Employee.HireDate),
                    SortDirection = ListSortDirection.Descending
                });
            }

            await GridRef.SetStateAsync(gridState);
        }
    }

    protected override void OnInitialized()
    {
        for (int i = 1; i <= 30; i++)
        {
            GridData.Add(new Employee()
            {
                Id = i,
                Name = $"Name {i}",
                Team = $"Team {i % 5 + 1}",
                HireDate = DateTime.Today.AddDays(-Random.Shared.Next(1, 3000)),
                IsOnLeave = i % 4 == 0 ? true : false
            });
        }
    }

    public class Employee
    {
        public int Id { get; set; }
        public string Name { get; set; } = string.Empty;
        public string Team { get; set; } = string.Empty;
        public DateTime HireDate { get; set; }
        public bool IsOnLeave { get; set; }
    }
}
@using Telerik.DataSource

<TelerikGrid @ref="@GridRef"
             Data="@GridData"
             Pageable="true"
             FilterMode="@GridFilterMode.FilterRow"
             Height="400px">
    <GridToolBarTemplate>
        <TelerikButton ThemeColor="@ThemeConstants.Button.ThemeColor.Success"
                       OnClick="@( () => SetGridFilters(false) )">Filter Grid by Team</TelerikButton>
        <TelerikButton ThemeColor="@ThemeConstants.Button.ThemeColor.Primary"
                       OnClick="@( () => SetGridFilters(true) )">Filter Grid by Team and HireDate</TelerikButton>
        <span class="k-separator"></span>
        <TelerikButton OnClick="@RemoveGridFilters">Remove All Filters</TelerikButton>
    </GridToolBarTemplate>
    <GridColumns>
        <GridColumn Field="@(nameof(Employee.Name))" Title="Employee Name" />
        <GridColumn Field="@(nameof(Employee.Team))" Title="Team" />
        <GridColumn Field="@(nameof(Employee.HireDate))" Title="Hire Date" DisplayFormat="{0:d}" />
        <GridColumn Field="@(nameof(Employee.IsOnLeave))" Title="Is On Leave" />
    </GridColumns>
</TelerikGrid>

@code {
    private TelerikGrid<Employee>? GridRef { get; set; }

    private List<Employee> GridData { get; set; } = new();

    private async Task SetGridFilters(bool shouldFilterSecondColumn)
    {
        if (GridRef != null)
        {
            var gridState = GridRef.GetState();

            // Find the Team CompositeFilterDescriptor if it exists.
            CompositeFilterDescriptor? teamCFD = gridState.FilterDescriptors.Cast<CompositeFilterDescriptor>()
                .Where(x => x.FilterDescriptors.Cast<FilterDescriptor>().First().Member == nameof(Employee.Team))
                .FirstOrDefault();

            if (teamCFD != null)
            {
                // Update the existing Team CompositeFilterDescriptor.

                var teamFilterDescriptors = teamCFD.FilterDescriptors.Cast<FilterDescriptor>();

                // When using a filter row, the column's CompositeFilterDescriptor
                // always contains one FilterDescriptor.

                FilterDescriptor firstTeamFD = teamFilterDescriptors.First();
                firstTeamFD.Operator = FilterOperator.IsEqualTo;
                firstTeamFD.Value = "Team 1";
            }
            else
            {
                // Create a new Team CompositeFilterDescriptor.

                var teamFdCollection = new FilterDescriptorCollection();

                teamFdCollection.Add(new FilterDescriptor()
                {
                    Member = nameof(Employee.Team),
                    MemberType = typeof(string),
                    Operator = FilterOperator.IsEqualTo,
                    Value = "Team 1"
                });

                // Add one CompositeFilterDescriptor per column.
                gridState.FilterDescriptors.Add(new CompositeFilterDescriptor()
                {
                    // The LogicalOperator property doesn't matter, because
                    // there is only one FilterDescritor in the CompositeFilterDescriptor.
                    FilterDescriptors = teamFdCollection
                });
            }

            // Find the HireDate CompositeFilterDescriptor if it exists.
            CompositeFilterDescriptor? hireDateCFD = gridState.FilterDescriptors.Cast<CompositeFilterDescriptor>()
                .Where(x => x.FilterDescriptors.Cast<FilterDescriptor>().First().Member == nameof(Employee.HireDate))
                .FirstOrDefault();

            if (hireDateCFD != null)
            {
                // Instead of changing the existing CompositeFilterDescriptor,
                // you can also remove it and create a new one.
                gridState.FilterDescriptors.Remove(hireDateCFD);
            }

            if (shouldFilterSecondColumn)
            {
                var hireDateFdCollection = new FilterDescriptorCollection();

                hireDateFdCollection.Add(new FilterDescriptor()
                {
                    Member = nameof(Employee.HireDate),
                    MemberType = typeof(DateTime),
                    Operator = FilterOperator.IsGreaterThanOrEqualTo,
                    Value = DateTime.Today.AddYears(-3)
                });

                gridState.FilterDescriptors.Add(new CompositeFilterDescriptor()
                {
                    FilterDescriptors = hireDateFdCollection
                });
            }

            await GridRef.SetStateAsync(gridState);
        }
    }

    private async Task RemoveGridFilters()
    {
        if (GridRef != null)
        {
            var gridState = GridRef.GetState();

            gridState.FilterDescriptors.Clear();

            await GridRef.SetStateAsync(gridState);
        }
    }

    protected override void OnInitialized()
    {
        for (int i = 1; i <= 30; i++)
        {
            GridData.Add(new Employee()
            {
                Id = i,
                Name = $"Name {i}",
                Team = $"Team {i % 5 + 1}",
                HireDate = DateTime.Today.AddDays(-Random.Shared.Next(1, 3000)),
                IsOnLeave = i % 4 == 0 ? true : false
            });
        }
    }

    public class Employee
    {
        public int Id { get; set; }
        public string Name { get; set; } = string.Empty;
        public string Team { get; set; } = string.Empty;
        public DateTime HireDate { get; set; }
        public bool IsOnLeave { get; set; }
    }
}
@using Telerik.DataSource

<TelerikGrid @ref="@GridRef"
             Data="@GridData"
             Pageable="true"
             FilterMode="@GridFilterMode.FilterMenu"
             Height="400px">
    <GridToolBarTemplate>
        <TelerikButton ThemeColor="@ThemeConstants.Button.ThemeColor.Success"
                       OnClick="@( () => SetTeamFilter(false) )">Filter Grid by Team 1</TelerikButton>
        <TelerikButton ThemeColor="@ThemeConstants.Button.ThemeColor.Primary"
                       OnClick="@( () => SetTeamFilter(true) )">Filter Grid by Team 1 or 3</TelerikButton>
        <span class="k-separator"></span>
        <TelerikButton OnClick="@RemoveGridFilters">Remove All Filters</TelerikButton>
    </GridToolBarTemplate>
    <GridColumns>
        <GridColumn Field="@(nameof(Employee.Name))" Title="Employee Name" />
        <GridColumn Field="@(nameof(Employee.Team))" Title="Team" />
        <GridColumn Field="@(nameof(Employee.HireDate))" Title="Hire Date" DisplayFormat="{0:d}" />
        <GridColumn Field="@(nameof(Employee.IsOnLeave))" Title="Is On Leave" />
    </GridColumns>
</TelerikGrid>

@code {
    private TelerikGrid<Employee>? GridRef { get; set; }

    private List<Employee> GridData { get; set; } = new();

    private async Task SetTeamFilter(bool shouldSetSecondFilter)
    {
        if (GridRef != null)
        {
            var gridState = GridRef.GetState();

            // Find the Team CompositeFilterDescriptor if it exists.
            CompositeFilterDescriptor? teamCFD = gridState.FilterDescriptors.Cast<CompositeFilterDescriptor>()
                .Where(x => x.FilterDescriptors.Cast<FilterDescriptor>().First().Member == nameof(Employee.Team))
                .FirstOrDefault();

            if (teamCFD != null)
            {
                // Update the existing Team CompositeFilterDescriptor.

                teamCFD.LogicalOperator = FilterCompositionLogicalOperator.Or;

                var teamFilterDescriptors = teamCFD.FilterDescriptors.Cast<FilterDescriptor>();

                // When using a filter menu, the column's CompositeFilterDescriptor
                // always contains two FilterDescriptors.

                FilterDescriptor firstTeamFD = teamFilterDescriptors.First();
                firstTeamFD.Operator = FilterOperator.IsEqualTo;
                firstTeamFD.Value = "Team 1";

                // Set a null FilterDescriptor Value and IsEqualTo Operator
                // to disable a filter.
                FilterDescriptor secondTeamFD = teamFilterDescriptors.Last();
                secondTeamFD.Operator = FilterOperator.IsEqualTo;
                secondTeamFD.Value = shouldSetSecondFilter ? "Team 3" : null;
            }
            else
            {
                // Create a new Team CompositeFilterDescriptor.

                var fdCollection = new FilterDescriptorCollection();

                fdCollection.Add(new FilterDescriptor()
                {
                    Member = nameof(Employee.Team),
                    MemberType = typeof(string),
                    Operator = FilterOperator.IsEqualTo,
                    Value = "Team 1"
                });

                fdCollection.Add(new FilterDescriptor()
                {
                    Member = nameof(Employee.Team),
                    MemberType = typeof(string),
                    Operator = FilterOperator.IsEqualTo,
                    Value = shouldSetSecondFilter ? "Team 3" : null
                });

                // Add one CompositeFilterDescriptor per column.
                gridState.FilterDescriptors.Add(new CompositeFilterDescriptor()
                {
                    LogicalOperator = FilterCompositionLogicalOperator.Or,
                    FilterDescriptors = fdCollection
                });
            }

            await GridRef.SetStateAsync(gridState);
        }
    }

    private async Task RemoveGridFilters()
    {
        if (GridRef != null)
        {
            var gridState = GridRef.GetState();

            gridState.FilterDescriptors.Clear();

            await GridRef.SetStateAsync(gridState);
        }
    }

    protected override void OnInitialized()
    {
        for (int i = 1; i <= 30; i++)
        {
            GridData.Add(new Employee()
            {
                Id = i,
                Name = $"Name {i}",
                Team = $"Team {i % 5 + 1}",
                HireDate = DateTime.Today.AddDays(-Random.Shared.Next(1, 3000)),
                IsOnLeave = i % 4 == 0 ? true : false
            });
        }
    }

    public class Employee
    {
        public int Id { get; set; }
        public string Name { get; set; } = string.Empty;
        public string Team { get; set; } = string.Empty;
        public DateTime HireDate { get; set; }
        public bool IsOnLeave { get; set; }
    }
}
@using Telerik.DataSource

<TelerikGrid @ref="@GridRef"
             Data="@GridData"
             Pageable="true"
             Sortable="true">
    <GridToolBarTemplate>
        <GridSearchBox />
        <TelerikButton Icon="@SvgIcon.Search"
                       ThemeColor="@ThemeConstants.Button.ThemeColor.Primary"
                       OnClick="@OnSearchButtonClick">Search Programmatically</TelerikButton>
        <TelerikButton Icon="@SvgIcon.X"
                       OnClick="@OnClearButtonClick">Clear Search</TelerikButton>
    </GridToolBarTemplate>
    <GridColumns>
        <GridColumn Field="@nameof(GridModel.Name)" />
        <GridColumn Field="@nameof(GridModel.Description)" />
    </GridColumns>
</TelerikGrid>

@code {
    private TelerikGrid<GridModel>? GridRef { get; set; }

    private List<GridModel> GridData { get; set; } = new();

    private async Task OnSearchButtonClick()
    {
        if (GridRef != null)
        {
            var gridState = GridRef.GetState();

            var searchString = $"{(char)Random.Shared.Next(97, 123)}{(char)Random.Shared.Next(97, 123)}";

            var cfd = new CompositeFilterDescriptor();

            cfd.LogicalOperator = FilterCompositionLogicalOperator.Or;
            cfd.FilterDescriptors = new FilterDescriptorCollection();

            // Add one FilterDesccriptor for each string column
            cfd.FilterDescriptors.Add(new FilterDescriptor()
            {
                Member = nameof(GridModel.Name),
                MemberType = typeof(string),
                Operator = FilterOperator.Contains,
                Value = searchString
            });
            cfd.FilterDescriptors.Add(new FilterDescriptor()
            {
                Member = nameof(GridModel.Description),
                MemberType = typeof(string),
                Operator = FilterOperator.Contains,
                Value = searchString
            });

            gridState.SearchFilter = cfd;

            await GridRef.SetStateAsync(gridState);
        }
    }

    private async Task OnClearButtonClick()
    {
        if (GridRef != null)
        {
            var gridState = GridRef.GetState();

            (gridState.SearchFilter as CompositeFilterDescriptor)?.FilterDescriptors.Clear();

            await GridRef.SetStateAsync(gridState);
        }
    }

    protected override void OnInitialized()
    {
        for (int i = 1; i <= 500; i++)
        {
            GridData.Add(new GridModel()
            {
                Id = i,
                Name = $"{(char)Random.Shared.Next(65, 91)}{(char)Random.Shared.Next(65, 91)} " +
                    $"{(char)Random.Shared.Next(65, 91)}{(char)Random.Shared.Next(65, 91)} {i}",
                Description = $"{(char)Random.Shared.Next(97, 123)}{(char)Random.Shared.Next(97, 123)} " +
                    $"{(char)Random.Shared.Next(97, 123)}{(char)Random.Shared.Next(97, 123)} {i}"
            });
        }
    }

    public class GridModel
    {
        public int Id { get; set; }
        public string Name { get; set; } = string.Empty;
        public string Description { get; set; } = string.Empty;
    }
}
@using Telerik.DataSource

<TelerikGrid @ref="@GridRef"
             Data="@GridData"
             Pageable="true"
             Groupable="true">
    <GridToolBarTemplate>
        <TelerikButton ThemeColor="@ThemeConstants.Button.ThemeColor.Success"
                       OnClick="@( () => SetGridGroups(false) )">Group Grid by Team</TelerikButton>
        <TelerikButton ThemeColor="@ThemeConstants.Button.ThemeColor.Primary"
                       OnClick="@( () => SetGridGroups(true) )">Group Grid by Team and IsOnLeave</TelerikButton>
        <span class="k-separator"></span>
        <TelerikButton OnClick="@RemoveGridGroups">Remove All Groups</TelerikButton>
    </GridToolBarTemplate>
    <GridColumns>
        <GridColumn Field="@(nameof(Employee.Name))" Title="Employee Name" />
        <GridColumn Field="@(nameof(Employee.Team))" />
        <GridColumn Field="@(nameof(Employee.HireDate))" Title="Hire Date" DisplayFormat="{0:d}" />
        <GridColumn Field="@(nameof(Employee.IsOnLeave))" Title="Is On Leave" />
    </GridColumns>
</TelerikGrid>

@code {
    private TelerikGrid<Employee>? GridRef { get; set; }

    private List<Employee> GridData { get; set; } = new();

    private async Task SetGridGroups(bool shouldGroupBySecondColumn)
    {
        if (GridRef != null)
        {
            var gridState = GridRef.GetState();

            // Remove any existing Grid groups
            // You can also modify or reorder existing GroupDescriptors.
            gridState.GroupDescriptors.Clear();

            gridState.GroupDescriptors.Add(new GroupDescriptor()
            {
                Member = nameof(Employee.Team),
                MemberType = typeof(string),
                // https://feedback.telerik.com/blazor/1544196-allow-sorting-the-grouped-column
                SortDirection = ListSortDirection.Ascending
            });

            if (shouldGroupBySecondColumn)
            {
                gridState.GroupDescriptors.Add(new GroupDescriptor()
                {
                    Member = nameof(Employee.IsOnLeave),
                    MemberType = typeof(bool),
                    // https://feedback.telerik.com/blazor/1544196-allow-sorting-the-grouped-column
                    SortDirection = ListSortDirection.Descending
                });
            }

            await GridRef.SetStateAsync(gridState);
        }
    }

    private async Task RemoveGridGroups()
    {
        if (GridRef != null)
        {
            var gridState = GridRef.GetState();

            gridState.GroupDescriptors.Clear();

            await GridRef.SetStateAsync(gridState);
        }
    }

    protected override void OnInitialized()
    {
        for (int i = 1; i <= 30; i++)
        {
            GridData.Add(new Employee()
            {
                Id = i,
                Name = $"Name {i}",
                Team = $"Team {i % 5 + 1}",
                HireDate = DateTime.Today.AddDays(-Random.Shared.Next(1, 3000)),
                IsOnLeave = i % 4 == 0 ? true : false
            });
        }
    }

    public class Employee
    {
        public int Id { get; set; }
        public string Name { get; set; } = string.Empty;
        public string Team { get; set; } = string.Empty;
        public DateTime HireDate { get; set; }
        public bool IsOnLeave { get; set; }
    }
}
<TelerikGrid @ref="@GridRef"
             Data="@CategoryData"
             Pageable="true"
             PageSize="2">
    <GridToolBarTemplate>
        <TelerikDropDownList Data="@CategoryData"
                             @bind-Value="@DropDownListCategoryId"
                             TextField="@nameof(Category.Name)"
                             ValueField="@nameof(Category.Id)">
        </TelerikDropDownList>
        <TelerikButton ThemeColor="@ThemeConstants.Button.ThemeColor.Success"
                       OnClick="@ExpandCategory">Expand Category</TelerikButton>
        <span class="k-separator"></span>
        <TelerikButton ThemeColor="@ThemeConstants.Button.ThemeColor.Primary"
                       OnClick="@ExpandAll">Expand All Categories</TelerikButton>
        <span class="k-separator"></span>
        <TelerikButton OnClick="@CollapseAll">Collapse All Categories</TelerikButton>
    </GridToolBarTemplate>
    <GridColumns>
        <GridColumn Field="@(nameof(Category.Id))" Width="80px" />
        <GridColumn Field="@(nameof(Category.Name))" Title="Category Name" />
    </GridColumns>
    <DetailTemplate Context="category">
        <TelerikGrid Data="@ProductData.Where(x => x.CategoryId == category.Id)">
            <GridColumns>
                <GridColumn Field="@(nameof(Product.Name))" Title="Product Name" />
                <GridColumn Field="@(nameof(Product.Price))" DisplayFormat="{0:c2}" />
                <GridColumn Field="@(nameof(Product.Quantity))" />
            </GridColumns>
        </TelerikGrid>
    </DetailTemplate>
</TelerikGrid>

@code {
    private TelerikGrid<Category>? GridRef { get; set; }

    private List<Category> CategoryData { get; set; } = new();

    private List<Product> ProductData { get; set; } = new();

    private int DropDownListCategoryId { get; set; } = 1;

    private async Task ExpandCategory()
    {
        if (GridRef != null)
        {
            var gridState = GridRef.GetState();

            var categoryToExpand = CategoryData.First(x => x.Id == DropDownListCategoryId);

            gridState.ExpandedItems.Add(categoryToExpand);

            await GridRef.SetStateAsync(gridState);
        }
    }

    private async Task ExpandAll()
    {
        if (GridRef != null)
        {
            var gridState = GridRef.GetState();

            gridState.ExpandedItems = CategoryData;

            await GridRef.SetStateAsync(gridState);
        }
    }

    private async Task CollapseAll()
    {
        if (GridRef != null)
        {
            var gridState = GridRef.GetState();

            gridState.ExpandedItems.Clear();

            await GridRef.SetStateAsync(gridState);
        }
    }

    protected override void OnInitialized()
    {
        var categoryCount = 3;

        for (int i = 1; i <= categoryCount; i++)
        {
            CategoryData.Add(new Category()
            {
                Id = i,
                Name = $"Category {i}"
            });
        }

        for (int i = 1; i <= 12; i++)
        {
            ProductData.Add(new Product()
            {
                Id = i,
                CategoryId = i % categoryCount + 1,
                Name = $"Product {i}",
                Price = Random.Shared.Next(1, 100) * 1.23m,
                Quantity = Random.Shared.Next(0, 100)
            });
        }
    }

    public class Category
    {
        public int Id { get; set; }
        public string Name { get; set; } = string.Empty;
    }

    public class Product
    {
        public int Id { get; set; }
        public int CategoryId { get; set; }
        public string Name { get; set; } = string.Empty;
        public decimal Price { get; set; }
        public int Quantity { get; set; }
    }
}
<TelerikGrid @ref="@GridRef"
             Data="@GridData"
             Pageable="true"
             Reorderable="true">
    <GridToolBarTemplate>
        <TelerikButton ThemeColor="@ThemeConstants.Button.ThemeColor.Success"
                       OnClick="@ReorderPriceAndQuantity">Reorder Price and Quantity</TelerikButton>
        <TelerikButton ThemeColor="@ThemeConstants.Button.ThemeColor.Primary"
                       OnClick="@MakeIdColumnLast">Make Id Column Last</TelerikButton>
        <span class="k-separator"></span>
        <TelerikButton OnClick="@ResetColumnOrder">Reset Column Order</TelerikButton>
    </GridToolBarTemplate>
    <GridColumns>
        <GridColumn Field="@(nameof(Product.Id))" Width="80px" />
        <GridColumn Field="@(nameof(Product.Name))" Title="Product Name" />
        <GridColumn Field="@(nameof(Product.Price))" DisplayFormat="{0:c2}" />
        <GridColumn Field="@(nameof(Product.Quantity))" />
        <GridColumn Field="@(nameof(Product.ReleaseDate))" DisplayFormat="{0:d}" />
    </GridColumns>
</TelerikGrid>

@code {
    private TelerikGrid<Product>? GridRef { get; set; }

    private List<Product> GridData { get; set; } = new();

    private async Task ReorderPriceAndQuantity()
    {
        if (GridRef != null)
        {
            var gridState = GridRef.GetState();

            // Get column by its index in the Grid markup.
            var priceColumnState = gridState.ColumnStates.ElementAt(2);
            var priceColumnIndex = priceColumnState.Index;

            // Get column by a parameter such as Field or Id.
            var quantityColumnState = gridState.ColumnStates.First(x => x.Field == nameof(Product.Quantity));
            var quantityColumnIndex = quantityColumnState.Index;

            priceColumnState.Index = quantityColumnIndex;
            quantityColumnState.Index = priceColumnIndex;

            await GridRef.SetStateAsync(gridState);
        }
    }

    private async Task MakeIdColumnLast()
    {
        if (GridRef != null)
        {
            var gridState = GridRef.GetState();

            var idColumnState = gridState.ColumnStates.First(x => x.Field == nameof(Product.Id));
            var oldIdIndex = idColumnState.Index;

            idColumnState.Index = gridState.ColumnStates.Count - 1;

            foreach (var columnState in gridState.ColumnStates)
            {
                // Decrement the indexes of all columns that were after Id.
                if (columnState.Field != nameof(Product.Id) && columnState.Index > oldIdIndex)
                {
                    --columnState.Index;
                }
            }

            await GridRef.SetStateAsync(gridState);
        }
    }

    private async Task ResetColumnOrder()
    {
        if (GridRef != null)
        {
            var gridState = GridRef.GetState();

            gridState.ColumnStates = new List<GridColumnState>();

            await GridRef.SetStateAsync(gridState);
        }
    }

    protected override void OnInitialized()
    {
        for (int i = 1; i <= 5; i++)
        {
            GridData.Add(new Product()
            {
                Id = i,
                Name = $"Product {i}",
                Price = Random.Shared.Next(1, 100) * 1.23m,
                Quantity = Random.Shared.Next(0, 100),
                ReleaseDate = DateTime.Today.AddDays(-Random.Shared.Next(150, 3000))
            });
        }
    }

    public class Product
    {
        public int Id { get; set; }
        public string Name { get; set; } = string.Empty;
        public decimal Price { get; set; }
        public int Quantity { get; set; }
        public DateTime ReleaseDate { get; set; }
    }
}

If you want to alter the filters for a specific column, do not use more than one FilterDescriptor in FilterRow mode, and more than two FilterDescriptors in FilterMenu mode. Otherwise additional descriptors will not show up in the UI. This means that you may need to replace or modify an existing descriptor, rather than add a new one.

Inactive filter descriptors in FilterMenu mode are distinguished by their null Value.

Equals Comparison

State properties that pertain to data items (for example, edited item or selected items) are typed according to the Grid model. If you restore such data, make sure to implement appropriate comparison checks - by default the .Equals() check for a class (object) is a reference check and the reference from the restored state is very unlikely to match the current reference in the Grid data. Thus, you may want to override the .Equals() method of the Grid model class, so that it compares by ID, or otherwise re-populate the models in the state object with the new model references from the Grid data.

Examples

You can find multiple examples for using the Grid state in the following Knowledge Base articles:

See Also

In this article