# 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:

* [The properties of the `GridState` object](https://www.telerik.com/blazor-ui/documentation/components/grid/state#information-in-the-grid-state).
* [How to set initial Grid configuration programmatically in `OnStateInit`](https://www.telerik.com/blazor-ui/documentation/components/grid/state#onstateinit).
* [How to detect user changes in the Grid state with `OnStateChanged`](https://www.telerik.com/blazor-ui/documentation/components/grid/state#onstatechanged).
* [How to use Grid methods to get and set the Grid state](https://www.telerik.com/blazor-ui/documentation/components/grid/state#methods).
* [Why you may need to override the `Equals` method of the Grid model class](https://www.telerik.com/blazor-ui/documentation/components/grid/state#equals-comparison).

## Information in the Grid State

The Grid state is a generic [class `GridState<TItem>`](https://www.telerik.com/blazor-ui/documentation/api/telerik.blazor.components.gridstate-1). The type depends on the type of the Grid model. The `GridState<TItem>` object exposes the following properties:

<style>
    .article table {
        table-layout: auto;
        word-break: normal;
    }
</style>

| Property            | Type                             | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| ------------------- | -------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `CollapsedGroups`   | `ICollection<int>`               | The indexes of all collapsed [groups](https://www.telerik.com/blazor-ui/documentation/components/grid/grouping/overview) when `LoadGroupsOnDemand="false"`, i.e. when not [loading groups on demand](https://www.telerik.com/blazor-ui/documentation/components/grid/grouping/load-on-demand).                                                                                                                                                                                                  |
| `ColumnStates`      | `ICollection<GridColumnState>`   | Information about each [column's reorder index, width, visibility, locked state, `Id` parameter value and `Field`](https://www.telerik.com/blazor-ui/documentation/components/grid/columns/bound). 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](https://www.telerik.com/blazor-ui/documentation/components/grid/editing/incell).                                                                                                                                                                                                                                                                                                                                                |
| `EditItem`          | `TItem`\*                        | The currently edited data item in [any edit mode](https://www.telerik.com/blazor-ui/documentation/components/grid/editing/overview).                                                                                                                                                                                                                                                                                                                                                            |
| `ExpandedItems`     | `ICollection<TItem>`             | The expanded data items, when [using `<DetailTemplate>` (hierarchy)](https://www.telerik.com/blazor-ui/documentation/components/grid/hierarchy).                                                                                                                                                                                                                                                                                                                                                |
| `FilterDescriptors` | `ICollection<IFilterDescriptor>` | A collection of [`CompositeFilterDescriptor`](https://www.telerik.com/blazor-ui/documentation/common-features/data-binding/descriptors#filtering), except the ones that relate to the [`GridSearchBox`](https://www.telerik.com/blazor-ui/documentation/components/grid/filter/searchbox).                                                                                                                                                                                                      |
| `GroupDescriptors`  | `ICollection<GroupDescriptor>`   | Information about currently applied [grouping](https://www.telerik.com/blazor-ui/documentation/components/grid/grouping/overview).                                                                                                                                                                                                                                                                                                                                                              |
| `InsertedItem`      | `TItem`\*                        | The data item that is being added in `Inline` or `Popup` edit mode. [Not applicable for `Incell` editing](https://www.telerik.com/blazor-ui/documentation/components/grid/editing/incell#events).                                                                                                                                                                                                                                                                                               |
| `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](https://www.telerik.com/blazor-ui/documentation/components/grid/paging). Some user actions reset the page index to 1, such as filtering or changing the page size.                                                                                                                                                                                                                                                                                                     |
| `SearchFilter`      | `IFilterDescriptor`              | The [`CompositeFilterDescriptor`](https://www.telerik.com/blazor-ui/documentation/common-features/data-binding/descriptors#filtering) that holds the filter descriptors for the [`GridSearchBox`](https://www.telerik.com/blazor-ui/documentation/components/grid/filter/searchbox).                                                                                                                                                                                                            |
| `SelectedItems`     | `ICollection<TItem>`             | The currently [selected data item(s)](https://www.telerik.com/blazor-ui/documentation/components/grid/selection/overview).                                                                                                                                                                                                                                                                                                                                                                      |
| `Skip`              | `int?`                           | The number of scrolled data items when using [virtual row scrolling](https://www.telerik.com/blazor-ui/documentation/components/grid/virtual-scrolling). In other words, this is the number of rows above the currently visible ones.                                                                                                                                                                                                                                                           |
| `SortDescriptors`   | `ICollection<SortDescriptor>`    | The currently applied [sorts](https://www.telerik.com/blazor-ui/documentation/components/grid/sorting).                                                                                                                                                                                                                                                                                                                                                                                         |
| `TableWidth`        | `string`                         | The sum of all visible column widths. The initial value is always `null` regardless of the column configuration. The `TableWidth` value changes during column resizing together with `ColumnStates` and the`OnStateChanged` event does not fire separately for it. When you resize a column programmatically, and all other columns already have widths, you must update the `TableWidth` too, otherwise the other columns will resize unexpectedly.                                            |

\* `TItem` is the Grid model type.

## Events

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

* [OnStateInit](https://www.telerik.com/blazor-ui/documentation/components/grid/state#onstateinit)
* [OnStateChanged](https://www.telerik.com/blazor-ui/documentation/components/grid/state#onstatechanged)

### 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](https://www.telerik.com/blazor-ui/documentation/components/grid/state#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.

> caption Using Grid OnStateInit

<demo metaUrl="client/grid/state-init/" height="500"></demo>

### OnStateChanged

`OnStateChanged` fires when the user performs an action that changes the value of a [property in the Grid state](https://www.telerik.com/blazor-ui/documentation/components/grid/state#information-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](https://www.telerik.com/blazor-ui/documentation/components/grid/state#information-in-the-grid-state). 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](https://www.telerik.com/blazor-ui/documentation/components/grid/editing/incell#events).
* `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.

> tip 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.

Find out how to [get the applied filtering, sorting and grouping criteria](https://www.telerik.com/blazor-ui/documentation/common-features/data-binding/descriptors).

> caption Using Grid OnStateChanged

<demo metaUrl="client/grid/state-changed/" height="700"></demo>

## Methods

The `GetState` and `SetStateAsync` methods of the [Grid instance](https://www.telerik.com/blazor-ui/documentation/components/grid/overview#blazor-grid-reference-and-methods) let you get and set the current Grid state on demand at any time *after* [`OnStateInit`](https://www.telerik.com/blazor-ui/documentation/components/grid/state#onstateinit).

* `GetState` returns the current Grid state, so you can save it or [retrieve specific information](https://www.telerik.com/blazor-ui/documentation/components/grid/state#information-in-the-grid-state). For example, you can [use `GetState` to get the current filters, sorts, and page number](https://www.telerik.com/blazor-ui/documentation/knowledge-base/grid-get-filtered-data). Or, you can [get the current Grid column properties like order index, width, and others)](https://www.telerik.com/blazor-ui/documentation/knowledge-base/grid-column-state).

* `SetStateAsync` receives an instance of a `GridState<TItem>` object and applies it to the Grid. For example, you can have a button that puts the Grid in a certain configuration programmatically, for example sort or filter the data, enter or exit edit mode, expand or collapse groups or detail Grids, etc.

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`](https://www.telerik.com/blazor-ui/documentation/components/grid/state#onstateinit) or [`OnStateChanged`](https://www.telerik.com/blazor-ui/documentation/components/grid/state#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](https://www.telerik.com/blazor-ui/documentation/components/grid/editing/overview) (such as [`OnRead`](https://www.telerik.com/blazor-ui/documentation/components/grid/manual-operations), `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.

> tip 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.

> tip If you want to set an initial state to the Grid, use a similar snippet, but in the [`OnStateInit event`](https://www.telerik.com/blazor-ui/documentation/components/grid/state#onstateinit)

```RAZOR.skip-repl Sorting
@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" />
            Clear Existing Sorting On Button Click
        </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; }
    }
}
```

```RAZOR FilterRow
@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; }
    }
}
```

```RAZOR FilterMenu
@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; }
    }
}
```

```RAZOR Search
@using Telerik.DataSource

<TelerikGrid @ref="@GridRef"
             Data="@GridData"
             Pageable="true"
             Sortable="true">
    <GridToolBar>
        <GridToolBarCustomTool>
            <TelerikButton Icon="@SvgIcon.Search"
                        ThemeColor="@ThemeConstants.Button.ThemeColor.Primary"
                        OnClick="@OnSearchButtonClick">Search Programmatically</TelerikButton>
            <TelerikButton Icon="@SvgIcon.X"
                        OnClick="@OnClearButtonClick">Clear Search</TelerikButton>
        </GridToolBarCustomTool>
        <GridToolBarSearchBoxTool />
    </GridToolBar>
    <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 is null)
        {
            return;
        }

        GridState<GridModel> gridState = GridRef.GetState();

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

        CompositeFilterDescriptor cfd = new();

        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 is null)
        {
            return;
        }

        GridState<GridModel> 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;
    }
}
```

```RAZOR Grouping
@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; }
    }
}
```

```RAZOR Hierarchy
<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; }
    }
}
```

```RAZOR Columns
<TelerikGrid @ref="@GridRef"
             Data="@GridData"
             Pageable="true"
             Reorderable="true"
             Resizable="true">
    <GridToolBarTemplate>
        <TelerikButton ThemeColor="@ThemeConstants.Button.ThemeColor.Success"
                       OnClick="@ReorderPriceAndQuantity">Reorder Price and Quantity</TelerikButton>
        <TelerikButton ThemeColor="@ThemeConstants.Button.ThemeColor.Success"
                       OnClick="@MakeIdColumnLast">Make Id Column Last</TelerikButton>
        <TelerikButton ThemeColor="@ThemeConstants.Button.ThemeColor.Success"
                       OnClick="@ResizeColumns">Resize Columns</TelerikButton>
        <span class="k-separator"></span>
        <TelerikButton OnClick="@ResetColumns">Reset Column Configuration</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 ResizeColumns()
    {
        if (GridRef != null)
        {
            var gridState = GridRef.GetState();
            int newColumnWidth = 160;

            foreach (GridColumnState columnState in gridState.ColumnStates)
            {
                columnState.Width = $"{newColumnWidth}px";
            }

            gridState.TableWidth = $"{newColumnWidth * gridState.ColumnStates.Count}px";

            await GridRef.SetStateAsync(gridState);
        }
    }

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

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

            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; }
    }
}
```

> note 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()`](https://learn.microsoft.com/en-us/dotnet/api/system.object.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](https://www.telerik.com/blazor-ui/documentation/knowledge-base/grid-save-load-state-localstorage), 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](https://www.telerik.com/blazor-ui/documentation/knowledge-base):

* [Save and load the Grid state from `localStorage`](https://www.telerik.com/blazor-ui/documentation/knowledge-base/grid-save-load-state-localstorage)
* [Save the Grid state in a WebAssembly app](https://www.telerik.com/blazor-ui/documentation/knowledge-base/grid-save-state-in-webassembly)
* [Override a user action that changes the Grid state, for example, sort descending first](https://www.telerik.com/blazor-ui/documentation/knowledge-base/grid-sort-descending)
* [Initiate programmatic editing or inserting of a Grid row](https://www.telerik.com/blazor-ui/documentation/knowledge-base/grid-add-edit-state)
* [Get current Grid column state (order index, width, and others)](https://www.telerik.com/blazor-ui/documentation/knowledge-base/grid-column-state)

## See Also

* [Live Demo: Grid State](https://demos.telerik.com/blazor-ui/grid/persist-state)
* [GridState API reference](https://www.telerik.com/blazor-ui/documentation/api/telerik.blazor.components.gridstate-1)
* [Blazor Grid](https://www.telerik.com/blazor-ui/documentation/components/grid/overview)
