Skip to main content

Controlled vs Uncontrolled

OGrid supports both uncontrolled and controlled state patterns for every stateful feature -- sorting, filtering, pagination, column visibility, row selection, and column order. You can also mix and match, controlling some features while leaving others to the grid.

Uncontrolled (Zero Config)

In uncontrolled mode, OGrid manages all state internally. Just pass your columns and data:

import { OGrid } from '@alaarab/ogrid-react-radix';

const columns = [
{ columnId: 'name', name: 'Name', sortable: true },
{ columnId: 'email', name: 'Email', filterable: { type: 'text' as const } },
{ columnId: 'role', name: 'Role' },
];

function App() {
return (
<OGrid
columns={columns}
data={people}
getRowId={(item) => item.id}
defaultPageSize={25}
defaultSortBy="name"
defaultSortDirection="asc"
/>
);
}

The grid handles sorting, filtering, pagination, and everything else on its own. Use the default* props to set initial values.

Controlled (You Own the State)

In controlled mode, you pass the current value and an onChange callback for each feature you want to control. This is useful when you need to sync grid state with a URL, persist it to storage, or coordinate with other components.

import { useState } from 'react';
import { OGrid, type IFilters } from '@alaarab/ogrid-react-radix';

function App() {
const [page, setPage] = useState(1);
const [pageSize, setPageSize] = useState(25);
const [sort, setSort] = useState({ field: 'name', direction: 'asc' as const });
const [filters, setFilters] = useState<IFilters>({});

// Sync to URL, localStorage, or server on every change
const handleFiltersChange = (newFilters: IFilters) => {
setFilters(newFilters);
saveToUrl(newFilters);
};

return (
<OGrid
columns={columns}
data={people}
getRowId={(item) => item.id}
page={page}
onPageChange={setPage}
pageSize={pageSize}
onPageSizeChange={setPageSize}
sort={sort}
onSortChange={setSort}
filters={filters}
onFiltersChange={handleFiltersChange}
/>
);
}

Partial Control

You do not have to control everything. Control only the features you need -- the rest stays uncontrolled:

function App() {
// Only control sort; filters, pagination, etc. are managed by the grid
const [sort, setSort] = useState({ field: 'name', direction: 'asc' as const });

return (
<OGrid
columns={columns}
data={people}
getRowId={(item) => item.id}
sort={sort}
onSortChange={setSort}
defaultPageSize={50}
/>
);
}

Controllable Prop Pairs

Every stateful feature follows the same pattern: a value prop and an onChange callback. When both are provided, the grid operates in controlled mode for that feature. When neither is provided, the grid manages it internally.

FeatureValue PropCallback PropDefault Prop
SortsortonSortChangedefaultSortBy, defaultSortDirection
FiltersfiltersonFiltersChange--
PagepageonPageChange--
Page sizepageSizeonPageSizeChangedefaultPageSize
Visible columnsvisibleColumnsonVisibleColumnsChange--
Selected rowsselectedRowsonSelectionChange--
Column ordercolumnOrderonColumnOrderChange--
tip

When using controlled mode, always pass both the value and the callback. Passing only the value without a callback will lock the feature in its initial state, since OGrid will not be able to update it.

Imperative API

For cases where you need to read or set state programmatically (e.g., a "Reset Filters" button), use the grid API ref:

import { useRef } from 'react';
import { OGrid, type IOGridApi } from '@alaarab/ogrid-react-radix';

function App() {
const gridRef = useRef<IOGridApi<Person>>(null);

const handleReset = () => {
gridRef.current?.setFilterModel({});
gridRef.current?.deselectAll();
};

return (
<>
<button onClick={handleReset}>Reset</button>
<OGrid
ref={gridRef}
columns={columns}
data={people}
getRowId={(item) => item.id}
/>
</>
);
}

The API ref works in both controlled and uncontrolled mode. See the Grid API reference for the full list of methods.

When to Use Which

ScenarioRecommendation
Simple table, no external state neededUncontrolled
URL-driven filters or sortControlled (filters, sort)
Persist column layout to localStorageControlled (visibleColumns, columnOrder) + getColumnState()
Server-side data with dataSourceControlled or uncontrolled -- both work with dataSource
Coordinate multiple gridsControlled (shared state between grids)