Server Data Loading
Unbalanced Rows (Tree Data) Copy Page Open additional page export options
Use the server data source to handle unbalanced hierarchies and load hierarchical data on demand.
Hierarchical data can include leaf rows at varying depths, a common pattern in
NoSQL databases. LyteNyte Grid supports these unbalanced hierarchies.
LyteNyte Grid’s server data source renders the rows returned by the server,
not the models defined in the grid state. For example, if the row group model
defines four grouping levels, the grid does not force every row to expand four times to reach a leaf node.
The server data source can display tree data of any depth, provided the
server’s data representation stays consistent across requests and responses.
The demo demonstrates this by creating a tree with varying branch
depths. It does not use row groups, but the same pattern applies.
Unbalanced Rows import " @1771technologies/lytenyte-pro/light-dark.css " ;
import { useMemo , useState } from " react " ;
import { Server } from " ./server.js " ;
import { LastModified , SizeRenderer } from " ./components.js " ;
import { Grid , useServerDataSource } from " @1771technologies/lytenyte-pro " ;
import { RowGroupCell } from " @1771technologies/lytenyte-pro/components " ;
const columns : Grid . Column <{ data : any }> [] = [
if ( p . loading ) return null ;
< div className = " font-bold " style = {{ paddingInlineStart: 18 }}>
{ id : " size " , name : " Size " , cellRenderer : SizeRenderer },
{ id : " last_modified " , name : " Modified " , cellRenderer : LastModified },
{ id : " type " , width : 100 , name : " Ext " },
export default function ServerDataDemo () {
const [ expansions , setExpansions ] = useState < Record < string , boolean | undefined >> ( { " /Documents " : true } ) ;
const ds = useServerDataSource < any > ( {
return Server ( params . requests ) ;
rowGroupExpansions : expansions ,
onRowGroupExpansionChange : setExpansions ,
const isLoading = ds . isLoading . useValue () ;
< div className = " ln-grid " style = {{ height: 500 }}>
return { viewport: { style: { scrollbarGutter: " stable " } } };
< div className = " bg-ln-gray-20/40 absolute left-0 top-0 z-20 h-full w-full animate-pulse " ></ div >
import type { Grid } from " @1771technologies/lytenyte-pro " ;
import type { ClassValue } from " clsx " ;
import { format } from " date-fns " ;
import { twMerge } from " tailwind-merge " ;
export function tw ( ... c : ClassValue [] ) {
return twMerge ( clsx ( ... c )) ;
const formatter = new Intl . NumberFormat ( " en-Us " , {
minimumFractionDigits : 2 ,
maximumFractionDigits : 2 ,
function formatKBtoMB ( kb : number ) {
if ( isNaN ( kb ) || kb < 0 ) return " 0 " ;
const formatted = formatter . format ( mb ) ;
// Add commas for readability
return `${ Number ( formatted ) . toLocaleString () }` ;
export function LastModified ({ column , row , api } : Grid . T . CellRendererParams <{ data : any }>) {
if ( ! api . rowIsLeaf ( row )) return null ;
const value = api . columnField ( column , row ) ;
< div className = " flex w-full items-baseline justify-end gap-1 tabular-nums " >
{ format ( value as string , " dd MMM yyyy hh:mm:ss " )}
export function SizeRenderer ({ column , row , api } : Grid . T . CellRendererParams <{ data : any }>) {
if ( ! api . rowIsLeaf ( row )) return null ;
const value = api . columnField ( column , row ) ;
< div className = " flex w-full items-baseline justify-end gap-1 tabular-nums " >
{ formatKBtoMB ( value as number )} < span className = " text-xs " >MB</ span >
} from " @1771technologies/lytenyte-pro " ;
import { data } from " ./data.js " ;
const sleep = () => new Promise ( ( res ) => setTimeout ( res , 200 )) ;
export async function Server ( reqs : DataRequest [] ) {
// Simulate latency and server work.
for ( const p of c . path ) {
const rows = Object . entries ( pathNode ) ;
data : rows . map < DataResponseLeafItem | DataResponseBranchItem > ( ([ name , file ]) => {
const id = c . path . join ( " / " ) + " / " + name ;
if ( " kind " in file ) return { kind : " leaf " , id , data : { ... file , name , depth : c . path . length } };
childCount : Object . values ( file ) . length ,
} satisfies DataResponse ;
This is one way to handle unbalanced hierarchies. Another common approach collapses row groups
with a single child. The server interprets state, and LyteNyte Grid renders the rows it returns.