Table: performance issue #4439
Replies: 10 comments 2 replies
-
See #4377 |
Beta Was this translation helpful? Give feedback.
-
This solves the problem because of the cursor and floating windows, in fact I found the problem locally and disabled it long ago, but the performance of the table itself is still there |
Beta Was this translation helpful? Give feedback.
-
That error should not happen anymore from 49.0.10. Tables are more performant now, but do not scale to 1000+ cells. We will optimize inserting/pasting large table next, but would recommend to use a void table node that supports lazy loading for larger tables. |
Beta Was this translation helpful? Give feedback.
-
Thanks for the explanation。Could you clarify what a void table node with lazy loading means in practice? Specifically:
|
Beta Was this translation helpful? Give feedback.
-
@dosu please answer |
Beta Was this translation helpful? Give feedback.
-
@claude please answer |
Beta Was this translation helpful? Give feedback.
-
A "void table node with lazy loading" in Plate means representing a large table as a non-editable block (void node) that only loads or renders its data when needed, usually asynchronously. Plate does not have a built-in or example implementation of this for tables—only for media-like elements such as images and Excalidraw drawings. See how void nodes are set up in BaseImagePlugin and BaseExcalidrawPlugin. For tables, you’d create a new custom element type (not extend TableElement), mark it as The current TableKit and TablePlugin only support fully editable tables and do not support void/lazy/async tables out of the box. For large or remote tables, the void node pattern is the recommended approach for performance and scalability. To reply, just mention @dosu. How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other |
Beta Was this translation helpful? Give feedback.
-
@claude please answer, give an example with Tanstack table. Use Plate MCP to confirm the api you're using is existing. |
Beta Was this translation helpful? Give feedback.
-
Claude finished @zbeyens's task —— View job Task List
Void Table Node with Lazy Loading in PlateA "void table node with lazy loading" is a non-editable block element that renders table data asynchronously, loading only what's visible. This approach prevents performance issues with large tables by avoiding rendering thousands of cells at once. Key Concepts
Implementation Example with Tanstack TableHere's a complete example of implementing a lazy-loaded table using Tanstack Table: // 1. Define the void table element type
import { type TElement, createSlatePlugin } from '@platejs/core';
export interface TVoidTableElement extends TElement {
type: 'void-table';
// Reference to external data source
dataId: string;
// Optional: cached row count for height calculation
rowCount?: number;
// Optional: column configuration
columns?: Array<{ id: string; header: string; width?: number }>;
}
// 2. Create the void table plugin
export const VoidTablePlugin = createSlatePlugin({
key: 'void-table',
node: {
isElement: true,
isVoid: true, // This makes it a void node
},
}); // 3. Create the void table component with Tanstack Table
import React, { useEffect, useMemo, useState } from 'react';
import {
createColumnHelper,
flexRender,
getCoreRowModel,
useReactTable,
} from '@tanstack/react-table';
import { useVirtualizer } from '@tanstack/react-virtual';
import { PlateRenderElementProps } from '@platejs/core';
import { TVoidTableElement } from './types';
interface TableData {
[key: string]: any;
}
export function VoidTableElement({
element,
attributes,
children,
}: PlateRenderElementProps<TVoidTableElement>) {
const [data, setData] = useState<TableData[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
// Container ref for virtual scrolling
const tableContainerRef = React.useRef<HTMLDivElement>(null);
// Fetch data based on dataId
useEffect(() => {
async function fetchData() {
try {
setLoading(true);
// Replace with your actual data fetching logic
const response = await fetch(`/api/table-data/${element.dataId}`);
const tableData = await response.json();
setData(tableData);
} catch (err) {
setError('Failed to load table data');
} finally {
setLoading(false);
}
}
fetchData();
}, [element.dataId]);
// Setup Tanstack Table
const columns = useMemo(() => {
if (!element.columns || element.columns.length === 0) return [];
const columnHelper = createColumnHelper<TableData>();
return element.columns.map((col) =>
columnHelper.accessor(col.id, {
header: col.header,
size: col.width,
})
);
}, [element.columns]);
const table = useReactTable({
data,
columns,
getCoreRowModel: getCoreRowModel(),
});
const { rows } = table.getRowModel();
// Setup virtualizer for row virtualization
const virtualizer = useVirtualizer({
count: rows.length,
getScrollElement: () => tableContainerRef.current,
estimateSize: () => 35, // Estimated row height
overscan: 10, // Number of rows to render outside visible area
});
const virtualRows = virtualizer.getVirtualItems();
const totalSize = virtualizer.getTotalSize();
const paddingTop = virtualRows.length > 0 ? virtualRows?.[0]?.start || 0 : 0;
const paddingBottom =
virtualRows.length > 0
? totalSize - (virtualRows?.[virtualRows.length - 1]?.end || 0)
: 0;
if (loading) {
return (
<div {...attributes}>
<div className="flex h-96 items-center justify-center">
<div className="text-muted-foreground">Loading table data...</div>
</div>
{children}
</div>
);
}
if (error) {
return (
<div {...attributes}>
<div className="flex h-96 items-center justify-center">
<div className="text-destructive">{error}</div>
</div>
{children}
</div>
);
}
return (
<div {...attributes} className="my-4">
<div
ref={tableContainerRef}
className="h-96 overflow-auto rounded border"
>
<table className="w-full">
<thead className="sticky top-0 bg-background">
{table.getHeaderGroups().map((headerGroup) => (
<tr key={headerGroup.id}>
{headerGroup.headers.map((header) => (
<th
key={header.id}
className="border-b px-4 py-2 text-left font-medium"
style={{ width: header.getSize() }}
>
{flexRender(
header.column.columnDef.header,
header.getContext()
)}
</th>
))}
</tr>
))}
</thead>
<tbody>
{paddingTop > 0 && (
<tr>
<td style={{ height: `${paddingTop}px` }} />
</tr>
)}
{virtualRows.map((virtualRow) => {
const row = rows[virtualRow.index];
return (
<tr key={row.id}>
{row.getVisibleCells().map((cell) => (
<td key={cell.id} className="border-b px-4 py-2">
{flexRender(
cell.column.columnDef.cell,
cell.getContext()
)}
</td>
))}
</tr>
);
})}
{paddingBottom > 0 && (
<tr>
<td style={{ height: `${paddingBottom}px` }} />
</tr>
)}
</tbody>
</table>
</div>
{/* This contains the void node's children (required by Slate) */}
<div style={{ display: 'none' }}>{children}</div>
</div>
);
} // 4. Helper to insert a void table
import { type SlateEditor, insertNodes } from '@platejs/slate';
export function insertVoidTable(
editor: SlateEditor,
dataId: string,
columns: Array<{ id: string; header: string; width?: number }>,
) {
insertNodes(editor, {
type: 'void-table',
dataId,
columns,
children: [{ text: '' }], // Required for void nodes
});
} // 5. Usage in your editor setup
import { createPlateEditor } from '@platejs/core/react';
import { VoidTablePlugin } from './VoidTablePlugin';
import { VoidTableElement } from './VoidTableElement';
const editor = createPlateEditor({
plugins: [
// ... other plugins
VoidTablePlugin.configure({
render: {
node: VoidTableElement,
},
}),
],
});
// Insert a void table
insertVoidTable(editor, 'large-dataset-1', [
{ id: 'id', header: 'ID', width: 100 },
{ id: 'name', header: 'Name', width: 200 },
{ id: 'email', header: 'Email', width: 250 },
{ id: 'status', header: 'Status', width: 120 },
]); Key Benefits
Advanced FeaturesYou can extend this pattern with:
Performance Comparison
This approach solves the performance issues mentioned in the issue by:
|
Beta Was this translation helpful? Give feedback.
-
I'll move this thread to discussions since the original issue is fixed. |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
-
Description
With the nextjs dev mod, there are obvious duplicate rendering or performance issues, especially with table components. For example, if I copy a table with 70 rows from an external source, it will directly report an error for rendering more than 1000 times. Or in large table scenarios, the dev mode becomes unusually laggy. Production mode is much better!
Reproduction URL
No response
Reproduction steps
1. Create a table with more than 70 rows 2. Edit one of the cells in dev mode
Plate version
48
Slate React version
plate48
Screenshots
Logs
Browsers
No response
Beta Was this translation helpful? Give feedback.
All reactions