react-native-smart-table
v0.1.2
Published
Secure, performant, dark-theme-first React Native table with virtualization, frozen columns, row/col span, and composable custom cells.
Maintainers
Readme
react-native-smart-table
Enterprise-grade, dark-theme-first React Native table with virtualization, frozen columns, row/col span, sorting, selection, search, and inline cell editing.
Installation
npm install react-native-smart-tablePeer dependencies
npm install @shopify/flash-list react-native-reanimated @react-native-community/datetimepickerFollow the native setup guides for each peer dependency:
- @shopify/flash-list
- react-native-reanimated
- @react-native-community/datetimepicker (optional — only required when using
inputType: 'date'inline editing)
Babel plugin — add the Reanimated plugin to your babel.config.js:
// Reanimated v3
plugins: ['react-native-reanimated/plugin']
// Reanimated v4 (uses react-native-worklets)
plugins: ['react-native-worklets/plugin']Quick start
import { SmartTable } from 'react-native-smart-table';
import type { SmartTableColumn } from 'react-native-smart-table';
type User = { id: string; name: string; role: string; score: number };
const COLUMNS: SmartTableColumn<User>[] = [
{ key: 'name', title: 'Name', width: 160, frozen: 'left' },
{ key: 'role', title: 'Role', flex: 1, minWidth: 100 },
{ key: 'score', title: 'Score', width: 80, sortable: true },
];
const DATA: User[] = [
{ id: '1', name: 'Alice', role: 'Admin', score: 95 },
{ id: '2', name: 'Bob', role: 'Editor', score: 72 },
{ id: '3', name: 'Carol', role: 'Viewer', score: 58 },
];
export function MyScreen() {
return (
<SmartTable
columns={COLUMNS}
data={DATA}
getRowId={(row, _index) => row.id}
/>
);
}Column definition (SmartTableColumn<T>)
| Prop | Type | Description |
|---|---|---|
| key | string | Column identifier |
| title | string? | Header label |
| width | number? | Fixed pixel width |
| flex | number? | Proportional width (CSS flex) |
| minWidth | number? | Width floor during flex distribution |
| maxWidth | number? | Width ceiling during flex distribution |
| frozen | 'left' \| 'right'? | Pin column to a panel |
| sortable | boolean? | Show sort indicator; enable sort toggle |
| accessor | (row: T) => unknown | Custom value extractor |
| render | (value, row) => ReactNode | Simple cell renderer |
| renderCell | (context) => ReactNode | Full-context renderer |
| renderHeader | (context) => ReactNode | Custom header renderer |
| dataType | 'string' \| 'number' \| 'date' \| 'boolean' \| 'custom' | Informs sort comparator |
Props (SmartTableProps<T>)
| Prop | Type | Default | Description |
|---|---|---|---|
| data | readonly T[] | — | Row data |
| columns | readonly SmartTableColumn<T>[] | — | Column definitions |
| getRowId | (row, index) => string | — | Required stable row identity |
| defaultRowHeight | number | 48 | Row height in px |
| stickyHeader | boolean | true | Pins header during scroll |
| loading | boolean | false | Shows loading state |
| error | unknown | — | Shows error state |
| stateComponents | { loading, empty, error } | — | Custom state UIs |
| selection | TableSelectionConfig<T> | — | Row selection (see below) |
| sorting | TableSortingConfig<T> | — | Column sorting (see below) |
| interaction | TableInteractionConfig<T> | — | Press handlers |
Sorting
import { useTableSorting } from 'react-native-smart-table';
function MyScreen() {
const sorting = useTableSorting({
data: DATA,
columns: COLUMNS,
enabled: true,
// mode: 'server' — skips client sort, fires onSortModelChange
});
return (
<SmartTable
columns={COLUMNS}
data={sorting.sortedData}
getRowId={(row) => row.id}
sorting={sorting}
/>
);
}Tap a sortable column header to cycle: unsorted → ascending ↑ → descending ↓ → unsorted.
Selection
import { useTableSelection } from 'react-native-smart-table';
function MyScreen() {
const selection = useTableSelection({
data: DATA,
getRowId: (row) => row.id,
enabled: true,
multiple: true,
onSelectionChange: (ids) => console.log([...ids]),
});
return (
<SmartTable
columns={COLUMNS}
data={DATA}
getRowId={(row) => row.id}
selection={selection}
/>
);
}A checkbox column is automatically prepended when selection.enabled is true. The header renders a select-all checkbox with indeterminate state.
Search
import { useTableSearch } from 'react-native-smart-table';
function MyScreen() {
const [query, setQuery] = useState('');
const { filteredData, matchCount } = useTableSearch({
data: DATA,
columns: COLUMNS,
query,
searchableColumns: ['name', 'role'], // omit to search all columns
});
return (
<>
<TextInput value={query} onChangeText={setQuery} placeholder="Search…" />
<SmartTable columns={COLUMNS} data={filteredData} getRowId={(r) => r.id} />
</>
);
}Inline cell editing
import { useTableEditing, CellEditor } from 'react-native-smart-table';
const COLUMNS: SmartTableColumn<User>[] = [
{
key: 'name',
title: 'Name',
width: 160,
renderCell: ({ value, row, rowIndex, columnKey }) => {
const config = editing.getCellConfig(row, rowIndex, columnKey);
if (editing.isCellEditing(row.id, columnKey) && config) {
return (
<CellEditor
config={config}
draftValue={editing.draftValue}
validationError={editing.validationError}
onDraftChange={editing.updateDraft}
onCommit={editing.commitEdit}
onCancel={editing.cancelEdit}
/>
);
}
return (
<TouchableOpacity onPress={() => editing.startEdit(row.id, columnKey, value, row, rowIndex)}>
<Text>{String(value)}</Text>
</TouchableOpacity>
);
},
},
];
function MyScreen() {
const [rows, setRows] = useState(DATA);
const editing = useTableEditing<User>({
getCellEditConfig: (_row, _idx, col) => {
if (col === 'name') return { inputType: 'text' };
if (col === 'score') return { inputType: 'number', validate: v => Number(v) < 0 ? 'Must be positive' : null };
if (col === 'role') return { inputType: 'select', options: [{ label: 'Admin', value: 'Admin' }, { label: 'Editor', value: 'Editor' }] };
return null;
},
onCellEditCommit: (rowId, columnKey, newValue) => {
setRows(prev => prev.map(r => r.id === rowId ? { ...r, [columnKey]: newValue } : r));
},
});
return <SmartTable columns={COLUMNS} data={rows} getRowId={(r) => r.id} />;
}Supported inputType values: 'text', 'number', 'select', 'date'.
Frozen columns
Set frozen: 'left' or frozen: 'right' on any column. The table renders three panels — left, scrollable center, right — and synchronises vertical scroll across all three.
const COLUMNS: SmartTableColumn<Row>[] = [
{ key: 'name', frozen: 'left', width: 160 },
{ key: 'details', flex: 1 },
{ key: 'actions', frozen: 'right', width: 80 },
];Row span / col span
Supply a getCellSpan prop to calculateTableLayout or pass it to the engine directly:
calculateTableLayout({
data,
columns,
getRowId: (r) => r.id,
availableWidth: 400,
defaultRowHeight: 48,
getCellSpan: (row, rowIndex, column) => {
if (column.key === 'category' && rowIndex === 0) return { colSpan: 2 };
return undefined;
},
});Theme
import { TableThemeProvider, createTableTheme, defaultDarkTheme } from 'react-native-smart-table';
const theme = createTableTheme(defaultDarkTheme, {
colors: { primary: '#7C3AED' },
});
<TableThemeProvider theme={theme}>
<SmartTable ... />
</TableThemeProvider>Layout engine (headless)
The layout engine is exported as a pure function for headless or custom-renderer use:
import { calculateTableLayout } from 'react-native-smart-table';
const layout = calculateTableLayout({
data,
columns,
getRowId: (r) => r.id,
availableWidth: 800,
defaultRowHeight: 48,
});
// layout.columns — resolved pixel widths and x offsets
// layout.rows — resolved y offsets and heights
// layout.cells — pixel-positioned cell geometry (x, y, width, height)
// layout.frozenColumns — { left, center, right }License
MIT © SaiSahitya
