In a database client, the results grid is the most heavily used component. It is where the developer spends most of their time: browsing results, sorting columns, editing a value. If the grid lags on 10,000 rows or if sorting reloads the whole page, the tool becomes a drag. In QoreDB, the Data Grid is designed to stay smooth on large datasets, while offering rich interactions such as engine-delegated sorting and inline editing.
DOM virtualization: rendering only what is visible
The principle of virtualization is simple: if the screen shows 30 rows, there is no point creating 100,000 DOM nodes. QoreDB uses TanStack Virtual to materialize in the DOM only the visible rows, plus a buffer of 10 rows above and below (the overscan). The rest of the space is occupied by two spacer elements whose height corresponds to the sum of the non-rendered rows. When the user scrolls, the outgoing rows are destroyed and new ones are created on the other side. The whole thing relies on a fixed estimate of 32 pixels per row, which lets the virtualization engine instantly compute which rows to render for a given scroll position.
This approach has a cost: variable-height rows are not supported natively. In practice, for a tabular Data Grid, rows have a constant height, which makes the fixed estimate perfectly suited. The gain is immediate: a table of 200,000 rows never generates more than 50 DOM nodes at the same time.
Memory optimization through Proxy
A query result contains N rows and M columns. The naive conversion into JavaScript objects would create N times M properties, an allocation proportional to the product of the two. QoreDB uses a different approach: each row is an ES6 Proxy backed by the array of raw values returned by the Rust backend. Access to a property (for example a column name) is resolved via a shared map that associates each column name with its index. The Proxy stores nothing more than the reference to the original array.
The advantage is twofold. On the one hand, the memory footprint stays linear with respect to the number of actual values (N times M values, not N times M object properties with their metadata). On the other hand, only the cells actually read by the rendering trigger any work. If virtualization displays only 30 rows out of 100,000, the other 99,970 remain as compact arrays with no property resolution at all.
Server-side sorting: delegating to the engine
The Data Grid supports two sorting modes depending on the context. In classic pagination mode (the result fits within a page loaded in memory), sorting is done client-side by TanStack Table, with a comparison function that handles numeric types, strings (locale-aware sorting), and NULL values (always last). This mode is instantaneous since the data is already in memory.
In infinite scroll mode, sorting automatically switches to the server side. When the user clicks a column header, the component does not try to sort the N rows loaded locally. It notifies the parent via a callback, which re-runs the query with an ORDER BY on the chosen column. The database engine sorts the entire dataset, and the first results are reloaded. It is the only way to get correct sorting when data is loaded in chunks of 100 rows.
The switch between the two modes is transparent to the user. The TanStack Table configuration switches to manualSorting mode when infinite scroll is active, which disables local sorting and lets the backend drive the order. When the sort changes, the scroll returns to the top and the data is reloaded from the beginning.
Infinite scroll and progressive loading
Infinite scroll loads data in chunks (chunks of 100 rows by default). A listener on the container's scroll event detects when the user approaches the bottom of the grid (a threshold of 500 pixels). At that point, the next chunk is requested from the backend with the current pagination, sorting, and filters. The new rows are concatenated to the existing data, and virtualization takes over to render only what is visible. Loading stops automatically when the total number of rows announced by the server is reached.
Inline editing: modifying a cell directly
Inline edit lets you click a cell, modify its value, and confirm. The useInlineEdit hook manages the entire cycle: activating the edit, parsing the entered value according to the column type, comparing with the original value, and sending the mutation to the backend. The input is a simple text field that appears in the cell. Confirmation happens on blur or on pressing Enter, cancellation on Escape.
The parsing is sensitive to the data type declared by the engine. A boolean field interprets the strings "true" and "false". A numeric field converts the input into a number. A JSON field attempts a JSON.parse. If the conversion fails, the raw value is kept as a string. The value "null" (case-insensitive) is always interpreted as an SQL NULL.
Before sending the mutation, the hook checks several conditions: the table must have a primary key (otherwise the UPDATE cannot target a specific row), the driver must support mutations, and the connection must not be read-only. The primary key of the current row is extracted to build the UPDATE's WHERE clause. On the Rust side, the update_row command goes through the Universal Query Interceptor, which checks the safety rules (environment, mutation policy) before executing the query on the driver.
Behavior adapted to the environment
Inline editing behaves differently depending on the connection's environment. In development, the change is applied immediately after confirmation. In staging or production, a confirmation dialog appears before sending the mutation. This behavior is not a configurable option: it is built into the editing flow and relies on the environment classification defined when the connection was set up.
When sandbox mode is active, inline edit does not touch the database at all. The changes are stored locally and displayed as visual differences in the grid. The user can then generate an SQL script containing all the accumulated changes, or discard everything without any write having reached the server.
The dual state and refs pattern
One implementation detail worth mentioning: the inline editing hook keeps the state of the cell being edited both in a React useState (to trigger re-renders) and in a useRef (for synchronous access). This pattern is necessary because the blur and keydown handlers must read the current value at the exact moment of the event, not the one from the last render. Without this double storage, a quick blur right after a keystroke could read a stale value and produce an incorrect mutation.
QoreDB's Data Grid combines virtualization, adaptive sorting, and typed inline editing to offer a smooth experience on real data volumes. Each layer has a precise role: TanStack Virtual manages the DOM, TanStack Table manages the tabular state, the Rust backend applies the safety rules and delegates to the right driver. The result is a component that stays responsive on 200,000 rows, sorts correctly in progressive loading mode, and allows editing a cell in one click without sacrificing safety.
Stay updated on new releases
Subscribe to get product releases, new drivers notifications, and technical tutorials.

