Modern applications often need to handle datasets too large to load entirely into a browser. To maintain optimal performance, LyteNyte Grid implements a strategy where only necessary data is loaded while keeping the majority on the server—commonly known as server-side or on-demand data loading.
LyteNyte Grid's server row data source enables partial data loading, transferring only what's needed to the browser at any given time.
As the name suggests, this data source requests information from an external source—typically your own server. The expected responses must adhere to interfaces defined by the LyteNyte Grid server data source specification.
LyteNyte Grid provides a useServerDataSource
hook to create a server data source. At minimum,
you must supply a dataFetcher
function responsible for requesting data from your server. Below
is a basic server data source setup:
Throughout this guide, we use mock implementations of the data fetcher function instead of actual server calls. This approach helps illustrate the core concepts of working with a server data source.
The dataFetcher
function is the workhorse of your data source implementation. It receives an array
of AsyncDataRequestBlock
objects representing data blocks the server should return. Each
AsyncDataRequestBlock
has the following interface:
export interface AsyncDataRequestBlock {
readonly id: string;
readonly blockKey: number;
readonly path: string[];
readonly rowStart: number;
readonly rowEnd: number;
readonly blockStart: number;
readonly blockEnd: number;
}
Here's what each field means:
id
: A unique identifier for this block, primarily used internally by LyteNyte Grid but also
available for your custom block identificationblockKey
: A number representing the block offset—if data is partitioned into blocks of 100 rows,
a blockKey
of 1 would correspond to rows 100-199 (block keys start at 0)path
: A path for this block, populated only when row groups are present. The path consists of
parent keys, while an empty path
indicates a root-level blockrowStart
: The index of the first row in this blockrowEnd
: The index of the last row in this blockblockStart
: The first block index—for example, if the blockKey
is 2 and block sizes are 100,
then blockStart
would be 200blockEnd
: The last block index for this blockThe server data source may request multiple blocks depending on the current view configuration. Complex views with row grouping and pivots typically create more blocks, while simpler views usually request just one or two blocks.
Your dataFetcher
function should return an AsyncDataResponse
object based on the
AsyncDataRequestBlock
parameters:
export type AsyncDataResponse = {
readonly rootCount?: number;
readonly reqTime: number;
readonly blocks: AsyncDataBlock[];
readonly topBlock?: AsyncDataBlockPinned;
readonly bottomBlock?: AsyncDataBlockPinned;
readonly totalBlock?: AsyncDataBlockTotal;
};
The AsyncDataResponse
may return more than what was requested, but at minimum it must return a
response for each requested block in the blocks
property.
Here's what each property represents:
rootCount
: The number of root rows in the grid. Without grouping, this equals the total
rowCount
. With row groups, it represents the count of top-level rows without parentsreqTime
: A Unix timestamp when the request was sent. Your server should return this value
unchanged as it's used internally to ignore outdated requestsblocks
: An array of responses for the requested blocks, containing AsyncDataBlock
objects:export type AsyncDataBlock = {
readonly blockKey: number;
readonly frame: {
readonly data: unknown[];
readonly ids: string[];
readonly pathKeys: (string | null)[];
readonly kinds: (RowGroupKind | RowLeafKind)[];
readonly childCounts: number[];
};
readonly size: number;
readonly path?: string[];
};
Each block contains:
blockKey
: Same as the request blockKey
frame
: Contains the block data as parallel arrays that must have equal length:
data
: The row dataids
: Unique identifiers for each rowpathKeys
: Path values for each rowkinds
: Row type indicators (RowGroupKind
is 2, RowLeafKind
is 1)childCounts
: Number of child rows (0 for leaf rows)size
: The total size of this blockpath
: The block's path (empty or omitted for root blocks)Additionally, your response can include pinned rows and totals using these interfaces:
export type AsyncDataBlockPinned = {
readonly frame: {
readonly data: unknown[];
readonly ids: string[];
};
};
export type AsyncDataBlockTotal = {
readonly frame: {
readonly data: unknown;
};
};
For AsyncDataBlockPinned
, the frame
property should contain arrays of equal length, just like in
blocks
.
While this may seem complex initially, the examples will clarify the concepts. You'll find that the server data source interface is straightforward, with most complexity residing in your server's data processing layer.
Row grouping increases the number of blocks the server data source manages and diversifies the responses your server needs to provide. The example below demonstrates a basic implementation of row grouping.
The useServerDataSource
hook accepts a blockSize
parameter that determines the size of each data
block requested from the server. The default value is 100 rows per block, but depending on your
users' internet connections, a blockSize
of 1000 is often appropriate for better performance.
Column pivoting is supported through the columnPivotsFetcher
property on the useServerDataSource
hook. This async callback should request column pivot definitions for the current configuration.
The data source automatically calls this function when column pivots are activated in LyteNyte Grid,
with all other functionality working normally.
The server data source is the most complex but also the most flexible data source type. Your server can return any row configuration, and it's responsible for applying filter models, sort models, and aggregation models based on the current grid state.
Some important limitations to keep in mind: