Skip to content

TanStack Data Grid Demo with RevoGrid

Use this demo to compare how TanStack Table data models can be rendered through RevoGrid in Vue and React applications.

Source code Git
vue
<template>
    <VGrid
        :source="source"
        :columns="rvColumns"
        hide-attribution
        @beforeedit="handleEdit"
    />
</template>

<script setup lang="ts">
import { VGrid, type ColumnProp } from '@revolist/vue3-datagrid'

import {
    useVueTable,
    getCoreRowModel,
    type ColumnDef,
    type RowData,
} from '@tanstack/vue-table'

import { computed, ref } from 'vue'
import { tanstackToRvGridColumns } from '../tanstack/utils'
import { makeData, type Person } from '../makeData'

declare module '@tanstack/vue-table' {
    interface TableMeta<TData extends RowData> {
        updateData: (
            rowIndex: number,
            columnId: ColumnProp,
            value: unknown
        ) => void
    }
}

// Define columns according tanstack guide
const columns: ColumnDef<Person>[] = [
    {
        header: 'Name',
        columns: [
            {
                accessorKey: 'firstName',
                header: 'First Name',
            },
            {
                accessorKey: 'lastName',
                header: 'Last Name',
            },
        ],
    },
]
const data = ref<Person[]>(makeData(5))

// define tanstack table
const table = useVueTable({
    data,
    columns,
    getCoreRowModel: getCoreRowModel(),
    meta: {
        updateData: (rowIndex: number, columnId: ColumnProp, value: any) => {
            data.value = data.value.map((row, index) => {
                if (index === rowIndex) {
                    return {
                        ...data.value[rowIndex]!,
                        [columnId]: value,
                    }
                }
                return row
            })
        },
    },
})

// get data from tanstack table
const source = computed(() => {
    const rows = table.getRowModel().flatRows
    return rows
})

const rvColumns = computed(() => {
    // transform tanstack to revogrid format
    return tanstackToRvGridColumns(table.getAllColumns())
})

function handleEdit(e: CustomEvent<HTMLRevoGridElementEventMap['beforeedit']>) {
    e.preventDefault()
    table.options.meta?.updateData(
        e.detail.model.index,
        e.detail.prop,
        e.detail.val
    )
}
</script>
tsx
import React, { useState, useMemo, useCallback } from 'react';
import { RevoGrid, type BeforeSaveDataDetails, type RevoGridCustomEvent } from '@revolist/react-datagrid';
import {
  useReactTable,
  getCoreRowModel,
  type ColumnDef,
} from '@tanstack/react-table';
import { tanstackToRvGridColumns } from '../tanstack/utils';
import { makeData, type Person } from '../makeData';

// Define TanStack columns
const columns: ColumnDef<Person>[] = [
  {
    header: 'Name',
    columns: [
      {
        accessorKey: 'firstName',
        header: 'First Name',
      },
      {
        accessorKey: 'lastName',
        header: 'Last Name',
      },
    ],
  },
];

const ExampleIntegration = () => {
  const [data, setData] = useState(makeData(5));

  // Set up TanStack table instance
  const table = useReactTable({
    data,
    columns,
    getCoreRowModel: getCoreRowModel(),
    meta: {
      updateData: (rowIndex, columnId, value) => {
        setData((old) =>
          old.map((row, index) => {
            if (index === rowIndex) {
              return {
                ...old[rowIndex],
                [columnId]: value,
              };
            }
            return row;
          })
        );
      },
    },
  });

  // Extract data and columns for RevoGrid
  const source = useMemo(() => {
    const rows = table.getRowModel().flatRows;
    return rows;
  }, [table]);

  const rvColumns = useMemo(() => {
    // Convert TanStack column definition to RevoGrid format
    return tanstackToRvGridColumns(table.getAllColumns());
  }, [table]);

  const handleEdit = useCallback((e: RevoGridCustomEvent<BeforeSaveDataDetails>) => {
    e.preventDefault();
    const { model, prop, val } = e.detail;
    table.options.meta?.updateData(model.index, prop, val);
  }, [table]);

  return (
    <RevoGrid
      source={source}
      columns={rvColumns}
      onBeforeedit={handleEdit}
      hideAttribution
    />
  );
};

export default ExampleIntegration;
ts
// Helper functions to convert tanstack to revogrid format
import type { ColumnGrouping, ColumnRegular } from "@revolist/vue3-datagrid";
import type { Column } from "@tanstack/vue-table";

export function tanstackToRvGridColumns(tanstackColumns: Column<any>[]) {
  return tanstackColumns.map((col) => {
    // Check if this column has children (group columns)
    if (col.columns?.length) {
      const rvGroup: ColumnGrouping = {
        name: col.columnDef.header,
        children: tanstackToRvGridColumns(col.columns),
      };
      return rvGroup; // Recursive call for child columns
    }
    const rvColumn: ColumnRegular = {
      prop: col.id, // Use col.id or accessorKey
      name: col.columnDef.header, // Direct mapping for column header
      sortable: !!col.getCanSort(), // Check if the column is sortable
      filter: col.getCanFilter() ? "customFilterType" : false, // Handle filters
    };

    // Handle custom sorting functions via cellCompare
    const sortingFn = col.getSortingFn();
    if (!!sortingFn) {
      rvColumn.cellCompare = (_, rowA: any, rowB: any) =>
        sortingFn(rowA, rowB, col.id);
      rvColumn.sortable = true; // Ensure sorting is enabled
    }

    // Handle filtering logic if provided
    const filterFn = col.getFilterFn();
    if (filterFn) {
      rvColumn.filter = "customFilterType"; // Assume custom type for filtering
    }

    rvColumn.cellParser = (model) => {
      return model.renderValue(col.id);
    };

    return rvColumn;
  });
}
ts
import { faker } from '@faker-js/faker'

export type Person = {
  avatar: string
  firstName: string
  lastName: string
  name: string
  age: number
  visits: number
  progress: number
  status: 'relationship' | 'complicated' | 'single'
  subRows?: Person[]
}

const range = (len: number) => {
  const arr: number[] = []
  for (let i = 0; i < len; i++) {
    arr.push(i)
  }
  return arr
}

const newPerson = (): Person => {
  const firstName = faker.person.firstName()
  const lastName = faker.person.lastName()
  return {
    avatar: faker.image.avatar(),
    firstName,
    lastName,
    name: `${firstName} ${lastName}`,
    age: faker.number.int(40),
    visits: faker.number.int(1000),
    progress: faker.number.int(100),
    status: faker.helpers.shuffle<Person['status']>([
      'relationship',
      'complicated',
      'single',
    ])[0]!,
  }
}

export function makeData(...lens: number[]) {
  const makeDataLevel = (depth = 0): Person[] => {
    const len = lens[depth]!
    return range(len).map((): Person => {
      return {
        ...newPerson(),
        subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined,
      }
    })
  }

  return makeDataLevel()
}