Row Data Sources
Server Data Source
This guide shows a basic example of the Controlled Data Source for loading data on an as-needed basis. Before reading this, please ensure you are familiar with controlled data sources in Graphite Grid.
Info
The implementation described below is just one approach to building a server-driven Graphite Grid. Depending on your backend infrastructure, a different approach may make more sense. Graphite Grid provides the flexibility to implement whatever data loading patterns best suit your data.
Caution
The example shown here is designed for clarity rather than production robustness. Network request error handling and data sanitization have been omitted. Use this example as inspiration for what is possible.
Server Endpoint
We start by creating a server endpoint. Our server endpoint will accept POST
requests and expect a JSON
body of
the form:
interface DataRequest {
rowStart: number;
rowEnd: number;
}
A hard-coded dataset is used for this example, but the data could come from your database. Here are some sample rows of our data:
const companyData = [
{
exchange: "NASDAQ",
symbol: "ESGR",
currency: "PHP",
sector: "Finance",
industry: "Property-Casualty Insurers",
price: 74.89,
volume: null,
},
{
exchange: "NASDAQ",
symbol: "LSXMA",
currency: "IDR",
sector: "Consumer Services",
industry: null,
price: 183.03,
volume: 154810,
},
// ... many more rows
];
Finally, we have our REST endpoint. The endpoint simply returns the rows for the given range.
async function POST(req: Request) {
const body = await req.json();
// Request validation omitted
const { rowStart, rowEnd } = body;
const rows = financeData.slice(rowStart, rowEnd);
const data = {
rows,
rowCount: financeData.length,
};
const response = new Response(JSON.stringify(data), {
headers: { "content-type": "application/json" },
status: 200,
});
return response;
}
Server-Driven Row Data Source
Next, we need to create a RowDataSourceControlled
implementation that
we can use to fetch data from our server. Even a basic implementation has
quite a bit of request state management. The code below is heavily commented on.
// Define the structure of company data received from the backend
interface CompanyDataRow {
exchange: string;
symbol: string;
currency: string;
sector: string;
industry: string;
price: number;
volume: number | null;
}
// Define the type for a row in the grid
// null is used as a sentinel value to indicate completion of async loading
type Row = CompanyDataRow | null;
// Implement a basic Row Data Source for server-side data
class ServerDataSource implements RowDataSourceControlled<Row> {
// Indicate that this is a controlled data source (required by Graphite Grid)
kind = "controlled" as const;
// Store loaded row nodes, indexed by row number
nodes: Record<number, RowNode<Row>>;
// Track the total number of rows in the dataset
rowCount: number;
// Reference to the Graphite Grid API for refreshing the grid
api!: GraphiteGridApi<Row>;
// Keep track of which pages of data have been requested
requestedPages: Set<string>;
// Initialize the row data source
constructor() {
this.nodes = {};
this.rowCount = 0;
this.requestedPages = new Set();
}
// Initialize the data source with the Graphite Grid API
// This method is called by Graphite Grid upon initialization
init = (api: GraphiteGridApi<Row>) => {
this.api = api;
// Start by requesting the first page of data
void this.requestPage(0, 50);
};
// Fetch a page of data from the server
// This is a basic implementation and should be enhanced for production use
requestPage = async (rowStart: number, rowEnd: number) => {
const page = `${rowStart}-${rowEnd}`;
if (this.requestedPages.has(page)) return;
// Mark this page as requested to avoid duplicate requests
this.requestedPages.add(page);
// Fetch data from the server
const response = await fetch("/api/server-side-data", {
method: "POST",
body: JSON.stringify({ rowStart, rowEnd }),
});
const data = await response.json();
// Process the received data and create row nodes
(data.rows as CompanyDataRow[]).forEach((row, i) => {
const rowIndex = i + rowStart;
const node = createRowNodeLeaf({
id: `${rowIndex}`,
data: row,
rowIndex: rowIndex,
});
this.nodes[rowIndex] = node;
});
// Update the total row count
this.rowCount = data.rowCount;
// Trigger a grid refresh to reflect the new data
this.api.refreshRowDataSource();
};
// Return the total number of rows in the dataset
getRowCount = () => {
return this.rowCount;
};
// Retrieve a row node by its ID
getRowById = (id: string) => {
const nodes = Object.values(this.nodes);
return nodes.find((node) => node.id === id) ?? null;
};
// Retrieve or create a row node for a given row index
getRowNode = ({ rowIndex }: GetRowNodeParams<Row>) => {
let node = this.nodes[rowIndex];
if (!node) {
// If the node doesn't exist, request the corresponding page of data
const rowStart = Math.floor(rowIndex / 50) * 50;
void this.requestPage(rowStart, rowStart + 50);
// Create a placeholder node while data is loading
// This ensures a stable reference for the row, even before data is available
node = this.nodes[rowIndex] = createRowNodeLeaf({
data: null,
id: `${rowIndex}`,
isLoading: true,
rowIndex: rowIndex,
});
}
return node;
};
}
Using the Row Data Source
Once a row data source has been defined, it can be used by Graphite Grid.
export function ServerDataGrid() {
const grid = useGraphiteGrid<Row>({
initial: {
columnDefinitions: financeColumns,
baseColumnDefinition: {
freeWidthRatio: 1,
},
rowDataSource: new ServerDataSource(),
},
});
return (
<div style={{ height: 300 }}>
<GraphiteGridDom state={grid} />
</div>
);
}
The example above shows a basic setup. It can be extended to implement more functionality, such as:
- Sorting
- Pivoting
- Filtering
- Row Selection
Tip
All of Graphite Grid's functionality is possible with a controlled data source. There is no trade-off versus a client data source. In fact, Graphite Grid converts the client data source internally to a controlled data source to power the client functionality.