Genesis
This commit is contained in:
232
README.md
Normal file
232
README.md
Normal file
@@ -0,0 +1,232 @@
|
||||
# ⚡ react-virtual-aggridautomat
|
||||
|
||||
> Opinionated, fully-virtualized AG Grid component powered by [react-automat](https://github.com/sebseb7/react-automat) — infinite-scroll data grids as React `PureComponent` with zero hooks, HOCs, or Context Providers.
|
||||
|
||||
[](https://www.ag-grid.com/)
|
||||
[](https://github.com/sebseb7/react-automat)
|
||||
[](https://react.dev/)
|
||||
|
||||
---
|
||||
|
||||
## What is this?
|
||||
|
||||
A reusable `<AutomatGrid />` PureComponent that wires AG Grid's [Infinite Row Model](https://www.ag-grid.com/react-data-grid/infinite-scrolling/) to a `react-automat` store. Your async data fetcher is the only thing you provide — the library handles:
|
||||
|
||||
- **Virtual row loading** — only visible rows exist in the DOM
|
||||
- **Block caching** — configurable cache size, debounced scroll loading
|
||||
- **Server-side sort & filter** — sort/filter changes flow through the automat and trigger datasource resets
|
||||
- **Reactive state** — loading indicators, error state, row counts, and revision tracking all live in the automat
|
||||
- **Zero hooks** — designed for `PureComponent` with synchronous constructor reads and fine-grained automat subscriptions
|
||||
|
||||
## Install
|
||||
|
||||
```bash
|
||||
npm install ag-grid-community ag-grid-react react-automat
|
||||
```
|
||||
|
||||
> This library is not yet published to npm. Clone or copy `src/lib/` into your project.
|
||||
|
||||
## Quick Start
|
||||
|
||||
### 1. Create a grid automat
|
||||
|
||||
```ts
|
||||
// myGridAutomat.ts
|
||||
import { createGridAutomat } from './lib';
|
||||
|
||||
export const myGridAutomat = createGridAutomat({
|
||||
fetchRows: async ({ startRow, endRow, sortModel, filterModel }) => {
|
||||
const res = await fetch(`/api/items?start=${startRow}&end=${endRow}`);
|
||||
const { items, total } = await res.json();
|
||||
return { rows: items, lastRow: total };
|
||||
},
|
||||
name: 'myGrid', // Visible in window.__AUTOMATS__
|
||||
blockSize: 100, // Rows per block (passed to AG Grid's cacheBlockSize)
|
||||
});
|
||||
```
|
||||
|
||||
### 2. Render the grid
|
||||
|
||||
```tsx
|
||||
// MyView.tsx
|
||||
import { PureComponent } from 'react';
|
||||
import { AutomatGrid } from './lib';
|
||||
import { myGridAutomat } from './myGridAutomat';
|
||||
|
||||
class MyView extends PureComponent {
|
||||
render() {
|
||||
return (
|
||||
<AutomatGrid
|
||||
automat={myGridAutomat}
|
||||
columnDefs={[
|
||||
{ field: 'id', maxWidth: 80 },
|
||||
{ field: 'name', flex: 2, filter: 'agTextColumnFilter' },
|
||||
{ field: 'price', valueFormatter: p => `€${p.value?.toFixed(2)}` },
|
||||
]}
|
||||
rowIdField="id"
|
||||
height="600px"
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. React to grid state
|
||||
|
||||
The automat is a standard `react-automat` store — subscribe to it from any PureComponent:
|
||||
|
||||
```tsx
|
||||
class StatusBar extends PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
loading: myGridAutomat.state.loading,
|
||||
totalRows: myGridAutomat.state.totalRows,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.unsub = myGridAutomat.subscribe(this, (s) => ({
|
||||
loading: s.loading,
|
||||
totalRows: s.totalRows,
|
||||
}));
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.unsub();
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
{this.state.loading ? 'Loading…' : `${this.state.totalRows} rows`}
|
||||
<button onClick={() => myGridAutomat.actions.reload()}>↻ Reload</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `createGridAutomat(options)`
|
||||
|
||||
Creates a `react-automat` store to back the grid.
|
||||
|
||||
| Option | Type | Default | Description |
|
||||
|---|---|---|---|
|
||||
| `fetchRows` | `(params: GridFetchParams) => Promise<GridFetchResult>` | **required** | Async function called by AG Grid's datasource for each block |
|
||||
| `name` | `string` | `null` | Automat registry name (visible in `window.__AUTOMATS__`) |
|
||||
| `blockSize` | `number` | `100` | Rows per cache block |
|
||||
|
||||
#### `GridFetchParams`
|
||||
|
||||
```ts
|
||||
{
|
||||
startRow: number;
|
||||
endRow: number;
|
||||
sortModel: SortModelItem[];
|
||||
filterModel: Record<string, unknown>;
|
||||
}
|
||||
```
|
||||
|
||||
#### `GridFetchResult<TRow>`
|
||||
|
||||
```ts
|
||||
{
|
||||
rows: TRow[];
|
||||
lastRow?: number; // Total count. undefined/-1 = unknown (infinite scroll continues)
|
||||
}
|
||||
```
|
||||
|
||||
#### Automat State
|
||||
|
||||
| Field | Type | Description |
|
||||
|---|---|---|
|
||||
| `ready` | `boolean` | `true` after first block loads |
|
||||
| `loading` | `boolean` | `true` while any block is in-flight |
|
||||
| `totalRows` | `number` | Known row count (`-1` if unknown) |
|
||||
| `sortModel` | `SortModelItem[]` | Current sort state |
|
||||
| `filterModel` | `Record<string, unknown>` | Current filter state |
|
||||
| `revision` | `number` | Increments on reload/sort/filter change |
|
||||
| `error` | `string \| null` | Last fetch error message |
|
||||
|
||||
#### Automat Actions
|
||||
|
||||
| Action | Description |
|
||||
|---|---|
|
||||
| `reload()` | Bumps revision, clears cache, forces full refetch |
|
||||
| `setSort(sortModel)` | Updates sort model + reloads |
|
||||
| `setFilter(filterModel)` | Updates filter model + reloads |
|
||||
|
||||
### `<AutomatGrid />` Props
|
||||
|
||||
| Prop | Type | Default | Description |
|
||||
|---|---|---|---|
|
||||
| `automat` | `Automat<GridAutomatState, GridAutomatActions>` | **required** | The grid automat store |
|
||||
| `columnDefs` | `ColDef[]` | **required** | AG Grid column definitions |
|
||||
| `defaultColDef` | `ColDef` | `{ flex: 1, minWidth: 100, sortable: true, resizable: true }` | Default column config |
|
||||
| `rowIdField` | `string` | `'id'` | Row data field used for stable row IDs |
|
||||
| `cacheBlockSize` | `number` | automat's `blockSize` | Override cache block size |
|
||||
| `rowBuffer` | `number` | `0` | Extra rows rendered outside viewport |
|
||||
| `infiniteInitialRowCount` | `number` | `200` | Scrollbar size hint |
|
||||
| `maxConcurrentDatasourceRequests` | `number` | `2` | Parallel block fetches |
|
||||
| `maxBlocksInCache` | `number` | `10` | Max cached blocks before eviction |
|
||||
| `blockLoadDebounceMillis` | `number` | `100` | Scroll debounce |
|
||||
| `height` | `string \| number` | `'100%'` | Container height |
|
||||
| `width` | `string \| number` | `'100%'` | Container width |
|
||||
| `rowHeight` | `number` | AG Grid default (42) | Row height in pixels |
|
||||
| `theme` | `string` | `'ag-theme-alpine ag-theme-alpine-dark'` | Container theme class |
|
||||
| `modules` | `Module[]` | InfiniteRowModel + Text/Number/Date/Custom filters | Additional AG Grid modules |
|
||||
| `onGridApiReady` | `(api: GridApi) => void` | — | Callback when AG Grid API is available |
|
||||
| `className` | `string` | — | Container CSS class |
|
||||
| `style` | `CSSProperties` | — | Container inline styles |
|
||||
|
||||
## Data Flow
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ AutomatGrid (PureComponent) │
|
||||
│ │
|
||||
│ constructor: reads automat.state synchronously │
|
||||
│ componentDidMount: subscribes with selector │
|
||||
│ componentDidUpdate: if revision changed → reset ds │
|
||||
│ │
|
||||
│ ┌──────────────┐ ┌────────────────────────┐ │
|
||||
│ │ AG Grid │────▶│ IDatasource.getRows() │ │
|
||||
│ │ (infinite │ │ calls automat._fetcher │ │
|
||||
│ │ row model) │◀────│ → successCallback() │ │
|
||||
│ └──────────────┘ └────────────────────────┘ │
|
||||
│ │ │ │
|
||||
│ onSortChanged ──────▶ automat.actions.setSort() │
|
||||
│ onFilterChanged ────▶ automat.actions.setFilter() │
|
||||
│ │ │ │
|
||||
│ └──── revision bump ─────┘ │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Running the Demo
|
||||
|
||||
```bash
|
||||
npm install
|
||||
npm run dev
|
||||
```
|
||||
|
||||
The demo generates 10,000 fake product rows and serves them through a simulated async fetcher with artificial latency. Open http://localhost:5173 to see infinite scroll in action.
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
src/
|
||||
├── lib/ # Reusable library
|
||||
│ ├── createGridAutomat.ts # Automat factory
|
||||
│ ├── AutomatGrid.tsx # PureComponent grid wrapper
|
||||
│ └── index.ts # Barrel exports
|
||||
└── demo/ # Demo app
|
||||
├── demoAutomat.ts # 10k fake rows + fetcher
|
||||
└── DemoGrid.tsx # Demo consumer component
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
0BSD
|
||||
Reference in New Issue
Block a user