<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Maks</title>
    <description>The latest articles on DEV Community by Maks (@kumakint).</description>
    <link>https://dev.to/kumakint</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F478820%2F00324fbf-3f03-4312-afb3-bcc9de95514d.jpg</url>
      <title>DEV Community: Maks</title>
      <link>https://dev.to/kumakint</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/kumakint"/>
    <language>en</language>
    <item>
      <title>Best Data Grid for Python Dash in 2026</title>
      <dc:creator>Maks</dc:creator>
      <pubDate>Sun, 26 Jul 2026 17:28:53 +0000</pubDate>
      <link>https://dev.to/kumakint/best-data-grid-for-python-dash-in-2026-2gio</link>
      <guid>https://dev.to/kumakint/best-data-grid-for-python-dash-in-2026-2gio</guid>
      <description>&lt;p&gt;Dash gives us a way to put Python data in a browser. There is a lot of stuff we can add there, but main focus of this article is data grids, its the most common request for us.&lt;/p&gt;

&lt;p&gt;I work on &lt;a href="https://rv-grid.com/" rel="noopener noreferrer"&gt;RevoGrid&lt;/a&gt;, so I know that option best. But RevoGrid is not the only choice, and different grids suit different projects. Here is the short, practical version of how the main Dash data grids compare in 2026.&lt;/p&gt;

&lt;h2&gt;
  
  
  The quick answer
&lt;/h2&gt;

&lt;p&gt;Here is the practical comparison I would use before building a prototype:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Grid&lt;/th&gt;
&lt;th&gt;Rendering approach&lt;/th&gt;
&lt;th&gt;Editing and spreadsheet tools&lt;/th&gt;
&lt;th&gt;Large-data approach&lt;/th&gt;
&lt;th&gt;Licensing and status&lt;/th&gt;
&lt;th&gt;Best fit&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;RevoGrid for Dash&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Virtualized DOM grid&lt;/td&gt;
&lt;td&gt;Editing, range selection, keyboard navigation, copy and paste&lt;/td&gt;
&lt;td&gt;Virtualizes both rows and columns&lt;/td&gt;
&lt;td&gt;MIT core, with optional Pro modules&lt;/td&gt;
&lt;td&gt;Fast, editable data applications&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Dash AG Grid&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Virtualized DOM grid&lt;/td&gt;
&lt;td&gt;Mature editing, selection and clipboard features&lt;/td&gt;
&lt;td&gt;Multiple row models, including enterprise options&lt;/td&gt;
&lt;td&gt;Community edition plus commercial Enterprise features&lt;/td&gt;
&lt;td&gt;Enterprise teams needing the broadest feature set&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Dash Glide Grid&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Canvas&lt;/td&gt;
&lt;td&gt;Spreadsheet-style editing, ranges, fill handle, copy and paste, undo and redo&lt;/td&gt;
&lt;td&gt;Efficient canvas rendering&lt;/td&gt;
&lt;td&gt;Open-source wrapper&lt;/td&gt;
&lt;td&gt;Apps that should feel more like a spreadsheet&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Dash DataTable&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;HTML table with virtualization options&lt;/td&gt;
&lt;td&gt;Editing, filtering, sorting and conditional formatting&lt;/td&gt;
&lt;td&gt;Works for moderate data; larger datasets need more care&lt;/td&gt;
&lt;td&gt;Deprecated; planned for removal from the core Dash API in Dash 5.0&lt;/td&gt;
&lt;td&gt;Maintaining existing Dash applications&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Dash Tabulator&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Virtual DOM table&lt;/td&gt;
&lt;td&gt;Common editing and table interactions&lt;/td&gt;
&lt;td&gt;Virtual DOM rendering&lt;/td&gt;
&lt;td&gt;Open-source wrapper; not every Tabulator feature is exposed&lt;/td&gt;
&lt;td&gt;Smaller projects with straightforward table needs&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fuxvkfmnuqsmnwn5jjztf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fuxvkfmnuqsmnwn5jjztf.png" alt="Five approaches to building a data grid for Python Dash" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The grids can look similar with ten rows. The differences become clearer when you add large datasets, editing, callbacks, custom cells, or commercial licensing.&lt;/p&gt;

&lt;h2&gt;
  
  
  RevoGrid for Dash
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://rv-grid.com/guide/dash/" rel="noopener noreferrer"&gt;RevoGrid for Dash&lt;/a&gt; is built for applications where the grid becomes a real workspace.&lt;/p&gt;

&lt;p&gt;Its open-source core includes virtual scrolling, inline editing, range selection, copy and paste, sorting, filtering, pinned rows and columns, and keyboard navigation. Both rows and columns are virtualized, so the browser only renders the cells currently in view.&lt;/p&gt;

&lt;p&gt;One detail matters a lot in Dash: when a user edits a cell, RevoGrid can send a small edit event back to Python instead of returning the entire dataset.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpd7vp1t49uemebh0jaud.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpd7vp1t49uemebh0jaud.png" alt="A compact grid edit event moving from the browser to a Python callback" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That keeps callbacks easier to understand and avoids moving thousands of unchanged rows after every edit.&lt;/p&gt;

&lt;p&gt;The trade-off is that JavaScript functions cannot be passed directly through a normal Python component property. Most standard grid behavior works naturally from Dash, but deeply custom JavaScript renderers or editors need a browser-side integration.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dash AG Grid
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://dash.plotly.com/dash-ag-grid" rel="noopener noreferrer"&gt;Dash AG Grid&lt;/a&gt; is the strongest alternative and probably the most familiar choice for large enterprise teams.&lt;/p&gt;

&lt;p&gt;It has mature documentation, a large feature set, several row models, and many examples. If your company already uses AG Grid, choosing its Dash wrapper may be the simplest decision.&lt;/p&gt;

&lt;p&gt;The main thing to check early is licensing. AG Grid Community covers many normal requirements, while features such as server-side row grouping, master/detail, tree data, and advanced aggregation belong to AG Grid Enterprise.&lt;/p&gt;

&lt;p&gt;This is not necessarily a problem. It just means you should compare your likely future requirements—not only the features in your first prototype.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dash Glide Grid
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/benweinberg89/dash-glide-grid" rel="noopener noreferrer"&gt;Dash Glide Grid&lt;/a&gt; uses canvas rendering and offers a smooth spreadsheet-style experience with range selection, copy and paste, a fill handle, undo and redo, and several rich cell types.&lt;/p&gt;

&lt;p&gt;Canvas is both its strength and its main trade-off. It can render a large visual surface very efficiently, but cells are not regular DOM elements. That changes how CSS customization, browser inspection, accessibility, and embedded interactive content work.&lt;/p&gt;

&lt;p&gt;It is worth testing if the product should feel closer to a spreadsheet than a normal web table.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dash DataTable
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://dash.plotly.com/datatable" rel="noopener noreferrer"&gt;Dash DataTable&lt;/a&gt; was the default table for many Dash applications for years. It still supports useful features such as editing, filtering, sorting, conditional formatting, and callbacks.&lt;/p&gt;

&lt;p&gt;However, it is deprecated and is expected to leave the core Dash API in Dash 5.0. That makes it difficult to recommend for a new application.&lt;/p&gt;

&lt;p&gt;There is no need to rewrite a stable app overnight. But if you are starting something new—or adding major table functionality—it makes sense to choose a grid with a clearer path forward.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dash Tabulator
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/preftech/dash-tabulator" rel="noopener noreferrer"&gt;Dash Tabulator&lt;/a&gt; wraps the open-source Tabulator table.&lt;/p&gt;

&lt;p&gt;It can be a good fit for a smaller project, especially if you already know Tabulator. The important limitation is that the Dash wrapper does not expose every feature of the underlying JavaScript library, so check its actual Python API before committing to it.&lt;/p&gt;

&lt;h2&gt;
  
  
  A small RevoGrid example
&lt;/h2&gt;

&lt;p&gt;Install the Dash package:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;dash-datagrid
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then create &lt;code&gt;app.py&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;dash&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Dash&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Input&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Output&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;html&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;dash_datagrid&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;RevoGrid&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Dash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;__name__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;rows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;order&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;A-100&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;customer&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Ada&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;amount&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;order&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;A-101&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;customer&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Grace&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;amount&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;85&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;order&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;A-102&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;customer&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Linus&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;amount&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;210&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="n"&gt;columns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;prop&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;order&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Order&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;readonly&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;prop&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;customer&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Customer&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sortable&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;prop&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;amount&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Amount&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sortable&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;filter&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;number&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;layout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="nc"&gt;RevoGrid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;orders-grid&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;columns&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;columns&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nb"&gt;filter&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;resize&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;height&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;360&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Pre&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Edit a cell&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;edit-output&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="nd"&gt;@callback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nc"&gt;Output&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;edit-output&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;children&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nc"&gt;Input&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;orders-grid&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;afteredit&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;prevent_initial_call&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;show_edit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;change&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;detail&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="nf"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Row &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;change&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rowIndex&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;change&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;prop&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; = &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;change&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;val&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;!r}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run it with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python app.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One easy detail to miss: give the grid an explicit height. RevoGrid fills its container, so a missing height is a common reason a new grid appears blank.&lt;/p&gt;

&lt;p&gt;If your data is already in Pandas, the conversion is simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;rows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dataframe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_dict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;records&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For timestamps, NumPy values, or missing values, normalizing through &lt;code&gt;DataFrame.to_json()&lt;/code&gt; first is safer.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I would choose
&lt;/h2&gt;

&lt;p&gt;For a new, editable Dash application, I would start with &lt;strong&gt;RevoGrid for Dash&lt;/strong&gt;. It gives you a capable MIT core, good spreadsheet-style interaction, virtualized rendering, and focused events for Python callbacks.&lt;/p&gt;

&lt;p&gt;I would choose &lt;strong&gt;Dash AG Grid&lt;/strong&gt; when an organization already uses AG Grid or needs specific Enterprise features. I would test &lt;strong&gt;Dash Glide Grid&lt;/strong&gt; for a canvas-heavy spreadsheet experience.&lt;/p&gt;

&lt;p&gt;For an existing &lt;strong&gt;Dash DataTable&lt;/strong&gt;, I would plan a migration rather than continue building new functionality around it. And for a smaller, simpler table, &lt;strong&gt;Dash Tabulator&lt;/strong&gt; may already be enough.&lt;/p&gt;

&lt;p&gt;The best test is not a ten-row demo. Load something close to your real dataset, edit several cells, paste a range from Excel, add a callback, and see how much data travels between the browser and Python. That usually makes the right choice much more obvious.&lt;/p&gt;

&lt;p&gt;You can try RevoGrid with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;dash-datagrid
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The full setup guide and API examples are in the &lt;a href="https://rv-grid.com/guide/dash/" rel="noopener noreferrer"&gt;RevoGrid Dash documentation&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>python</category>
      <category>plotly</category>
      <category>datastructures</category>
      <category>webdev</category>
    </item>
    <item>
      <title>MS Project Gantt Is Dead — What Now?</title>
      <dc:creator>Maks</dc:creator>
      <pubDate>Fri, 10 Jul 2026 17:54:40 +0000</pubDate>
      <link>https://dev.to/kumakint/ms-project-gantt-is-dead-what-now-46dp</link>
      <guid>https://dev.to/kumakint/ms-project-gantt-is-dead-what-now-46dp</guid>
      <description>&lt;p&gt;For years, “Gantt chart” and “Microsoft Project” were almost interchangeable.&lt;/p&gt;

&lt;p&gt;The workflow is simple: create tasks, connect dependencies, check the critical path, assign resources, and export the plan for the next meeting.&lt;/p&gt;

&lt;p&gt;Not anymore.....&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fv9anq284qswkwb2z20ew.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fv9anq284qswkwb2z20ew.png" alt="MSP Gantt is no longer with us" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Microsoft retired &lt;strong&gt;Project for the web&lt;/strong&gt; and moved its capabilities into &lt;strong&gt;Microsoft Planner&lt;/strong&gt;. &lt;strong&gt;Project Online&lt;/strong&gt; is also scheduled to retire on September 30, 2026.&lt;/p&gt;

&lt;p&gt;To be honest: the Microsoft Project desktop app is not disappearing. But Microsoft’s cloud project-management strategy is shifting to Planner—and that gives product teams a reason to reconsider where scheduling should live.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Gantt is moving into the product
&lt;/h2&gt;

&lt;p&gt;Multiple of our customers still ask the same questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;When should work start and finish?&lt;/li&gt;
&lt;li&gt;Which task is blocking another?&lt;/li&gt;
&lt;li&gt;What happens if a task slips?&lt;/li&gt;
&lt;li&gt;Which deadline is at risk?&lt;/li&gt;
&lt;li&gt;Who is overallocated?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And they expect those answers inside the software they already use: an ERP, construction platform, manufacturing system, product-roadmap tool, logistics application, or internal operations dashboard.&lt;/p&gt;

&lt;p&gt;For these products, sending users to a separate planning tool creates friction and splits the source of truth. The better option may be to embed planning directly into the application.&lt;/p&gt;

&lt;h2&gt;
  
  
  A real Gantt is more than task bars?
&lt;/h2&gt;

&lt;p&gt;Drawing a colored bar on a timeline is easy. Building a schedule users can trust is much harder.&lt;/p&gt;

&lt;p&gt;If Task B depends on Task A, the system needs to calculate what happens when Task A moves. If a task can only start after a permit date, the constraint must be respected. If a resource is assigned to two tasks at once, the system should report the conflict—or resolve it predictably.&lt;/p&gt;

&lt;p&gt;A serious scheduling component therefore needs more than drag and drop. At minimum, look for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;task hierarchy and summary rows;&lt;/li&gt;
&lt;li&gt;milestones;&lt;/li&gt;
&lt;li&gt;dependencies with lead and lag;&lt;/li&gt;
&lt;li&gt;working calendars and holidays;&lt;/li&gt;
&lt;li&gt;constraints and deadlines;&lt;/li&gt;
&lt;li&gt;manual and automatic scheduling;&lt;/li&gt;
&lt;li&gt;resource allocation;&lt;/li&gt;
&lt;li&gt;critical path and slack;&lt;/li&gt;
&lt;li&gt;clear diagnostics when rules conflict.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The last point matters. Silent date changes quickly destroy trust. Users need to understand not only &lt;em&gt;that&lt;/em&gt; a task moved, but &lt;em&gt;why&lt;/em&gt; it moved.&lt;/p&gt;

&lt;h2&gt;
  
  
  Don’t lose the table
&lt;/h2&gt;

&lt;p&gt;Our business applications do not start as Gantt charts. They start as tables.&lt;/p&gt;

&lt;p&gt;Those contain custom columns, editors, validation, permissions, filters, keyboard workflows, status badges, and business-specific data. We could just use another model and component to build Gantt but....&lt;/p&gt;

&lt;p&gt;A better approach is simple:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Keep the table. Add scheduling intelligence and a timeline on top.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is the idea behind &lt;a href="https://rv-grid.com/gantt" rel="noopener noreferrer"&gt;RevoGrid Gantt&lt;/a&gt;. The existing data grid handles the table experience—rendering, virtualization, editing, selection, and keyboard interaction. The Gantt layer adds task bars, dependencies, calendars, resources, baselines, critical path, and scheduling rules.&lt;/p&gt;

&lt;p&gt;Users can work with familiar rows and columns while gaining a visual project plan. Developers keep control of their data model instead of rebuilding the workflow around a standalone project-management tool.&lt;/p&gt;

&lt;h2&gt;
  
  
  What should you do now?
&lt;/h2&gt;

&lt;p&gt;Before choosing a replacement for Microsoft Project, start with your actual workflow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;List the scheduling concepts your users depend on: tasks, dependencies, calendars, resources, baselines, constraints, actuals, and custom fields.&lt;/li&gt;
&lt;li&gt;Decide where the source of truth should live—your backend, an ERP, Dataverse, or another system.&lt;/li&gt;
&lt;li&gt;Preserve the existing table experience, including editors, validation, permissions, and keyboard behavior.&lt;/li&gt;
&lt;li&gt;Treat scheduling conflicts as part of the product UX, not as hidden engine errors.&lt;/li&gt;
&lt;li&gt;Introduce complexity gradually: tasks first, then dependencies, calendars, critical path, and resource planning.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The Gantt is not dead. The silo is.
&lt;/h2&gt;

&lt;p&gt;Microsoft Project shaped how a generation thinks about project scheduling. But the future of Gantt is not limited to standalone planning software.&lt;/p&gt;

&lt;p&gt;Some teams will move to Planner. Others will choose a complete Gantt suite. And many product teams will bring scheduling into the applications where work already happens.&lt;/p&gt;

&lt;p&gt;For that last group, the direction is clear:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Keep the grid. Add the timeline. Preserve the context.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Further reading: &lt;a href="https://support.microsoft.com/en-us/office/frequently-asked-questions-about-microsoft-planner-d1a2d4e6-a4d7-408c-a48a-31caaa267de5" rel="noopener noreferrer"&gt;Microsoft’s Planner FAQ&lt;/a&gt; and the &lt;a href="https://rv-grid.com/gantt/" rel="noopener noreferrer"&gt;RevoGrid Gantt documentation&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>gantt</category>
      <category>msp</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Make AI Agents See Your Website</title>
      <dc:creator>Maks</dc:creator>
      <pubDate>Fri, 10 Jul 2026 17:25:20 +0000</pubDate>
      <link>https://dev.to/kumakint/make-ai-agents-see-your-website-1d23</link>
      <guid>https://dev.to/kumakint/make-ai-agents-see-your-website-1d23</guid>
      <description>&lt;p&gt;AI coding agents are now part of the developer workflow. Whether we like that shift or hate it, users ask Codex, Cursor, Claude Code, GitHub Copilot, and other tools to install packages, wire examples, migrate code, and explain APIs. For &lt;a href="https://rv-grid.com/blog/datagrid" rel="noopener noreferrer"&gt;JavaScript data grids&lt;/a&gt;, that often means asking an agent to build columns, editors, filters, Pivot views, or Gantt timelines.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmfxq9zpyn52feo7gxyif.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmfxq9zpyn52feo7gxyif.jpg" alt="AI Agent Matters Image" width="800" height="451"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We ran into the obvious problem: the docs were written for people. A person can use the sidebar, search, breadcrumbs, examples, and product context. An agent often starts with a URL and a vague instruction. If it cannot find the right entry point quickly, it guesses.&lt;/p&gt;

&lt;p&gt;There are several ways to make a site easier for agents to use: an MCP server for live retrieval, skill bundles for structured on-demand context, and &lt;code&gt;llms.txt&lt;/code&gt; as a public discovery layer.&lt;/p&gt;

&lt;p&gt;That is where &lt;code&gt;llms.txt&lt;/code&gt; helps. It is a public, text-first entry point that tells AI agents where the important documentation lives: installation guides, API reference, examples, migration notes, troubleshooting, full text exports, and any richer machine-readable bundles.&lt;/p&gt;

&lt;p&gt;The goal is not to “optimize for bots” at the expense of people. The goal is to make sure that when a user asks an agent to build with RevoGrid, the agent can find the same source material a careful developer would read first.&lt;/p&gt;

&lt;h2&gt;
  
  
  Is &lt;code&gt;llms.txt&lt;/code&gt; Actually Used?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://llmstxt.org/" rel="noopener noreferrer"&gt;&lt;code&gt;llms.txt&lt;/code&gt; began as a proposal&lt;/a&gt;, published by Answer.AI co-founder Jeremy Howard in September 2024.&lt;/p&gt;

&lt;p&gt;There is meaningful adoption among major developer platforms:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://developers.openai.com/api/docs/llms.txt" rel="noopener noreferrer"&gt;OpenAI publishes an &lt;code&gt;llms.txt&lt;/code&gt; index&lt;/a&gt; with Markdown versions of its API documentation and a combined &lt;code&gt;llms-full.txt&lt;/code&gt; export.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://platform.claude.com/llms.txt" rel="noopener noreferrer"&gt;Anthropic publishes an &lt;code&gt;llms.txt&lt;/code&gt; index&lt;/a&gt; for its developer documentation.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://developers.cloudflare.com/style-guide/how-we-docs/ai-consumability/" rel="noopener noreferrer"&gt;Cloudflare documents its AI-consumability strategy&lt;/a&gt; and publishes both per-product and site-wide &lt;code&gt;llms.txt&lt;/code&gt; and &lt;code&gt;llms-full.txt&lt;/code&gt; files.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.stripe.com/llms.txt" rel="noopener noreferrer"&gt;Stripe&lt;/a&gt; and &lt;a href="https://vercel.com/llms.txt" rel="noopener noreferrer"&gt;Vercel&lt;/a&gt; expose public documentation indexes in the same format.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.mintlify.com/docs/ai/llmstxt" rel="noopener noreferrer"&gt;Mintlify automatically generates &lt;code&gt;llms.txt&lt;/code&gt; and &lt;code&gt;llms-full.txt&lt;/code&gt;&lt;/a&gt; for documentation sites hosted on its platform.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What &lt;code&gt;llms.txt&lt;/code&gt; Is For
&lt;/h2&gt;

&lt;p&gt;Use it to answer these questions quickly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What is this product or library?&lt;/li&gt;
&lt;li&gt;Where are the most important getting started docs?&lt;/li&gt;
&lt;li&gt;Where is the API reference?&lt;/li&gt;
&lt;li&gt;Where are examples, migration notes, and troubleshooting pages?&lt;/li&gt;
&lt;li&gt;Is there a full text export or machine-readable bundle?&lt;/li&gt;
&lt;li&gt;Is there an MCP server or agent-specific package?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example shape:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# Product Documentation&lt;/span&gt;

&lt;span class="gu"&gt;## Primary Docs&lt;/span&gt;
&lt;span class="p"&gt;
-&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;Installation&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="sx"&gt;https://rv-grid.com/guide/installation&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;: Install and configure the package.
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;API Reference&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="sx"&gt;https://rv-grid.com/guide/api/revoGrid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;: Public TypeScript API.
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;Migration Guide&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="sx"&gt;https://rv-grid.com/guide/migration&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;: Upgrade notes.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How to Add &lt;code&gt;llms.txt&lt;/code&gt; to a Documentation Site
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Choose the canonical site origin, for example &lt;code&gt;https://rv-grid.com&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Generate a Markdown index at &lt;code&gt;/llms.txt&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Link to the highest-signal docs first.&lt;/li&gt;
&lt;li&gt;Include the full docs corpus if you publish one.&lt;/li&gt;
&lt;li&gt;Include agent-specific formats such as &lt;code&gt;.skill&lt;/code&gt; bundles or MCP instructions.&lt;/li&gt;
&lt;li&gt;Link to it from your human-facing AI or agent documentation, and optionally include it in your sitemap.&lt;/li&gt;
&lt;li&gt;Make sure auth middleware allows public access.&lt;/li&gt;
&lt;li&gt;Regenerate it whenever docs change.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For most docs sites, &lt;code&gt;llms.txt&lt;/code&gt; can be generated from the same content collection, sidebar, or route manifest that powers the website. That is important. If the AI-facing docs drift from the real docs, agents will confidently produce stale answers.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to Include in &lt;code&gt;llms.txt&lt;/code&gt;
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;installation and package names;&lt;/li&gt;
&lt;li&gt;framework-specific setup for React, Vue, Angular, Svelte, or plain JavaScript;&lt;/li&gt;
&lt;li&gt;API reference and TypeScript types;&lt;/li&gt;
&lt;li&gt;examples and live demos;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If your product has paid, enterprise, or advanced modules, say so directly. Agents need to know whether a feature is core, Pro, Enterprise, experimental, or deprecated. Ambiguity here turns into broken code later.&lt;/p&gt;

&lt;h2&gt;
  
  
  Split Documentation Skill
&lt;/h2&gt;

&lt;p&gt;For large docs, a split skill is usually better than asking an agent to read one giant file. The RevoGrid skill gives agents a &lt;code&gt;SKILL.md&lt;/code&gt; index and one reference file per docs page.&lt;/p&gt;

&lt;p&gt;This structure is no longer limited to one agent. &lt;a href="https://www.anthropic.com/engineering/equipping-agents-for-the-real-world-with-agent-skills" rel="noopener noreferrer"&gt;Anthropic introduced Agent Skills&lt;/a&gt; as folders containing instructions, scripts, and resources, and later released the format as an open standard. &lt;a href="https://developers.openai.com/codex/skills" rel="noopener noreferrer"&gt;OpenAI's Codex documentation&lt;/a&gt; also recommends a &lt;code&gt;SKILL.md&lt;/code&gt; file with optional &lt;code&gt;references/&lt;/code&gt;, &lt;code&gt;scripts/&lt;/code&gt;, and &lt;code&gt;assets/&lt;/code&gt; directories. Both describe progressive disclosure: the agent sees a small description first and loads the full skill or individual references only when the task needs them. The format and current ecosystem are documented at &lt;a href="https://agentskills.io/" rel="noopener noreferrer"&gt;Agent Skills&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This is the best format when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the docs are large;&lt;/li&gt;
&lt;li&gt;pages have similar headings;&lt;/li&gt;
&lt;li&gt;the agent needs exact page-level context;&lt;/li&gt;
&lt;li&gt;examples need surrounding explanation;&lt;/li&gt;
&lt;li&gt;users work in tools that support skill-style uploads or local reference folders.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Better for Large Docs: Split Reference Files
&lt;/h2&gt;

&lt;p&gt;For coding agents, the better structure is closer to how a human reads docs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;product-docs/
├── SKILL.md
└── references/
    ├── getting-started/
    │   └── installation.md
    ├── api-reference/
    │   └── grid.md
    └── guides/
        └── migration.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;SKILL.md&lt;/code&gt; is the index and operating guide. It explains when to use the docs and links to focused reference files. This mirrors the progressive-disclosure model documented by both &lt;a href="https://developers.openai.com/codex/skills" rel="noopener noreferrer"&gt;OpenAI&lt;/a&gt; and &lt;a href="https://agentskills.io/specification" rel="noopener noreferrer"&gt;the Agent Skills specification&lt;/a&gt;: advertise a small amount of metadata, then load detailed instructions and references only when relevant.&lt;/p&gt;

&lt;p&gt;Each file under &lt;code&gt;references/&lt;/code&gt; should represent one real documentation page. The agent can read the index, choose two or three relevant files, and avoid loading a massive corpus into context.&lt;/p&gt;

&lt;p&gt;This improves answer quality because the model sees complete local context for the page it selected: title, URL, description, source path, examples, and surrounding explanation. It also makes failures easier to debug because you can see which reference file the agent used.&lt;/p&gt;

&lt;h2&gt;
  
  
  Recommended Reference File Format
&lt;/h2&gt;

&lt;p&gt;Each split reference file should be self-contained:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# Page Title&lt;/span&gt;

URL: https://rv-grid.com/guide/page/
Source: src/content/docs/guide/page.mdx
Description: Short page summary.

Page content starts here...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keep the original code examples. Remove interactive-only wrappers that do not help in text form. If your docs use MDX components, unwrap or remove them carefully so the remaining Markdown still reads naturally.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Write About This Publicly
&lt;/h2&gt;

&lt;p&gt;This is not only for bots. Developers are already trying to understand how their docs should work with agents, and there is not much practical writing from teams that have actually wired this into a docs pipeline.&lt;/p&gt;

&lt;p&gt;That is the reason to write about it. Not because another SEO page needs to exist, but because someone else will hit the same problem: they publish a full text export, point an agent at it, and still get an answer that misses the important part.&lt;/p&gt;

&lt;p&gt;The useful searches are real:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;“How do I make my docs work with AI agents?”&lt;/li&gt;
&lt;li&gt;“What should be in llms.txt?”&lt;/li&gt;
&lt;li&gt;“Why is llms-full.txt ignored by coding agents?”&lt;/li&gt;
&lt;li&gt;“How do I package documentation as a skill?”&lt;/li&gt;
&lt;li&gt;“How should API docs be structured for Cursor, Codex, or Claude Code?”&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The page should answer those questions directly. If it does that, search visibility is a side effect of being useful, not the whole point.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mistakes we made
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Making &lt;code&gt;llms.txt&lt;/code&gt; Too Large
&lt;/h3&gt;

&lt;p&gt;If &lt;code&gt;llms.txt&lt;/code&gt; becomes another full corpus, it stops being useful as an index. Keep it short and link out.&lt;/p&gt;

&lt;h3&gt;
  
  
  Relying Only on &lt;code&gt;llms-full.txt&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;A full export is helpful, but it is not enough for large docs. Agents often grep it and miss important context.&lt;/p&gt;

&lt;h3&gt;
  
  
  Splitting Without an Index
&lt;/h3&gt;

&lt;p&gt;Reference files need a good &lt;code&gt;SKILL.md&lt;/code&gt; or manifest. Without an index, the agent still has to guess which file to open.&lt;/p&gt;

&lt;h3&gt;
  
  
  Publishing Stale AI Docs
&lt;/h3&gt;

&lt;p&gt;Do not write a separate AI manual by hand unless you can maintain it. Generate from the same docs source whenever possible.&lt;/p&gt;

&lt;h3&gt;
  
  
  Treating &lt;code&gt;llms.txt&lt;/code&gt; as &lt;code&gt;robots.txt&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;llms.txt&lt;/code&gt; provides content and navigation; it does not grant or deny crawler access. Keep using &lt;code&gt;robots.txt&lt;/code&gt; and the documented controls for each crawler when access policy matters.&lt;/p&gt;

&lt;h3&gt;
  
  
  Promising an AI Ranking Boost
&lt;/h3&gt;

&lt;p&gt;There is no reliable evidence that &lt;code&gt;llms.txt&lt;/code&gt; alone improves AI citations or search visibility. The &lt;a href="https://ahrefs.com/blog/what-is-llms-txt/" rel="noopener noreferrer"&gt;Ahrefs study&lt;/a&gt;, &lt;a href="https://medium.com/@kaispriestersbach/the-llms-txt-is-dead-more-precisely-a-dud-ab7bee4f469c" rel="noopener noreferrer"&gt;critical field reports&lt;/a&gt;, and &lt;a href="https://www.reddit.com/r/SEO/comments/1pqi6uv/is_llmtxt_useful_and_beneficial_for_better_ai/" rel="noopener noreferrer"&gt;practitioner discussion on Reddit&lt;/a&gt; all show why the claim remains disputed. Measure requests in server logs and evaluate agent answers against real tasks instead of assuming an effect.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Practical Checklist
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Keep &lt;code&gt;llms.txt&lt;/code&gt; short and navigational.&lt;/li&gt;
&lt;li&gt;Keep &lt;code&gt;llms-full.txt&lt;/code&gt; as a full fallback.&lt;/li&gt;
&lt;li&gt;Add split reference files for large documentation sets.&lt;/li&gt;
&lt;li&gt;Add MCP support when your users work in tools that can retrieve live context.&lt;/li&gt;
&lt;li&gt;Include canonical URLs in every generated page.&lt;/li&gt;
&lt;li&gt;Preserve code fences exactly.&lt;/li&gt;
&lt;li&gt;Use stable slugs and deterministic output.&lt;/li&gt;
&lt;li&gt;Make the files public and easy to fetch.&lt;/li&gt;
&lt;li&gt;Mention the machine-readable docs from your human docs.&lt;/li&gt;
&lt;li&gt;Regenerate AI docs from the same source as the website.&lt;/li&gt;
&lt;li&gt;Test whether agents can answer representative questions with the published files.&lt;/li&gt;
&lt;li&gt;Monitor server logs separately for ordinary pages, Markdown pages, and &lt;code&gt;llms.txt&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Keep crawler access policy in &lt;code&gt;robots.txt&lt;/code&gt;, not &lt;code&gt;llms.txt&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Sources and Further Reading
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Specifications and First-Party Guidance
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://llmstxt.org/" rel="noopener noreferrer"&gt;&lt;code&gt;llms.txt&lt;/code&gt; proposal&lt;/a&gt; — the original purpose, suggested Markdown structure, and examples.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://developers.openai.com/api/docs/llms.txt" rel="noopener noreferrer"&gt;OpenAI API documentation index&lt;/a&gt; — a live first-party &lt;code&gt;llms.txt&lt;/code&gt; implementation with Markdown page links and a full export.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://developers.cloudflare.com/style-guide/how-we-docs/ai-consumability/" rel="noopener noreferrer"&gt;Cloudflare: AI consumability&lt;/a&gt; — a detailed implementation covering indexes, full exports, per-page Markdown, and token efficiency.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.mintlify.com/docs/ai/llmstxt" rel="noopener noreferrer"&gt;Mintlify: &lt;code&gt;llms.txt&lt;/code&gt;&lt;/a&gt; — automatic generation, discovery headers, authentication behavior, and file structure.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.anthropic.com/engineering/equipping-agents-for-the-real-world-with-agent-skills" rel="noopener noreferrer"&gt;Anthropic: Equipping agents with Agent Skills&lt;/a&gt; — the motivation and architecture behind reusable skill folders.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://developers.openai.com/codex/skills" rel="noopener noreferrer"&gt;OpenAI: Build skills&lt;/a&gt; — Codex skill structure, progressive disclosure, installation locations, and authoring practices.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://agentskills.io/specification" rel="noopener noreferrer"&gt;Agent Skills open specification&lt;/a&gt; — the portable &lt;code&gt;SKILL.md&lt;/code&gt; format and validation rules.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.anthropic.com/engineering/writing-tools-for-agents" rel="noopener noreferrer"&gt;Anthropic: Writing effective tools for agents&lt;/a&gt; — guidance on tool descriptions, agent-friendly documentation, response design, and evaluation.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Adoption, Analysis, and Debate
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://platform.claude.com/llms.txt" rel="noopener noreferrer"&gt;Anthropic's public &lt;code&gt;llms.txt&lt;/code&gt;&lt;/a&gt;, &lt;a href="https://docs.stripe.com/llms.txt" rel="noopener noreferrer"&gt;Stripe's public &lt;code&gt;llms.txt&lt;/code&gt;&lt;/a&gt;, and &lt;a href="https://vercel.com/llms.txt" rel="noopener noreferrer"&gt;Vercel's public &lt;code&gt;llms.txt&lt;/code&gt;&lt;/a&gt; — examples from widely used developer platforms.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://llmstxthub.com/websites/anthropic-llms-txt" rel="noopener noreferrer"&gt;llms.txt Hub: Anthropic&lt;/a&gt; — a third-party directory view that makes the Anthropic implementation easier to inspect and discover.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://ahrefs.com/blog/what-is-llms-txt/" rel="noopener noreferrer"&gt;Ahrefs: What Is &lt;code&gt;llms.txt&lt;/code&gt;, and Should You Care About It?&lt;/a&gt; — adoption and request data plus a skeptical assessment of SEO claims.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://medium.com/@kaispriestersbach/the-llms-txt-is-dead-more-precisely-a-dud-ab7bee4f469c" rel="noopener noreferrer"&gt;Kai Spriestersbach: “The &lt;code&gt;llms.txt&lt;/code&gt; is dead. More precisely: a dud.”&lt;/a&gt; — a forceful counterargument based on observed bot requests.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.reddit.com/r/SEO/comments/1pqi6uv/is_llmtxt_useful_and_beneficial_for_better_ai/" rel="noopener noreferrer"&gt;Reddit r/SEO practitioner discussion&lt;/a&gt; — community experiences and the continuing disagreement about measurable AI visibility benefits.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Final Thought
&lt;/h2&gt;

&lt;p&gt;The goal is not to feed the model more text. The goal is to help the agent choose the right text.&lt;/p&gt;

&lt;p&gt;For a small project, &lt;code&gt;llms.txt&lt;/code&gt; plus clean Markdown pages may be enough. For larger libraries, frameworks, and enterprise components, add levels: a short index, a full fallback corpus, split reference files, and MCP for live retrieval.&lt;/p&gt;

&lt;p&gt;That is the model we use for RevoGrid: make the docs easy for humans to browse and easy for AI agents to discover, select, and read. The fact that major platforms publish &lt;code&gt;llms.txt&lt;/code&gt; and formally support Skills gives this approach credibility; honest measurement and multiple retrieval paths keep it grounded.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>javascript</category>
      <category>agentskills</category>
    </item>
    <item>
      <title>Build Collaborative Spreadsheets and Data Grids</title>
      <dc:creator>Maks</dc:creator>
      <pubDate>Tue, 23 Jun 2026 10:26:49 +0000</pubDate>
      <link>https://dev.to/kumakint/build-collaborative-spreadsheets-and-data-grids-1pj8</link>
      <guid>https://dev.to/kumakint/build-collaborative-spreadsheets-and-data-grids-1pj8</guid>
      <description>&lt;p&gt;Daily we do not start by saying:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Let’s build Google Sheets inside our application.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We start:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“We need an editable table.”&lt;/p&gt;
&lt;/blockquote&gt;


&lt;br&gt;
  &lt;p id="workflow-escalation-title"&gt;Then the customer asks us for one more "little" thing.&lt;/p&gt;



&lt;ol&gt;

    &lt;li&gt;

      &lt;span&gt;Copy and paste from Excel&lt;/span&gt;
      &lt;span&gt;📋 😊&lt;/span&gt;
    &lt;/li&gt;

    &lt;li&gt;

      &lt;span&gt;Formulas&lt;/span&gt;
      &lt;span&gt;📐 🤔&lt;/span&gt;
    &lt;/li&gt;

    &lt;li&gt;

      &lt;span&gt;Comments&lt;/span&gt;
      &lt;span&gt;💬 😕&lt;/span&gt;
    &lt;/li&gt;

    &lt;li&gt;

      &lt;span&gt;Who changed what?&lt;/span&gt;
      &lt;span&gt;👀 😩&lt;/span&gt;
    &lt;/li&gt;

    &lt;li&gt;

      &lt;span&gt;Several people editing the same data at the same time&lt;/span&gt;
      &lt;span&gt;👥 😰&lt;/span&gt;
    &lt;/li&gt;

  &lt;/ol&gt;


&lt;p&gt;And now things are getting complicated. At that point, the table is no longer just a table. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fadbn6f7uwxidojki8cua.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fadbn6f7uwxidojki8cua.png" alt=" " width="800" height="389"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That means the grid must handle more than rendering rows and columns. It needs real-time editing, user presence, conflict resolution, validation, permissions, audit history, and safe recovery when something goes wrong.&lt;/p&gt;

&lt;p&gt;We'll explain here how to think about collaborative spreadsheet-like applications with &lt;a href="https://rv-grid.com/blog/datagrid" rel="noopener noreferrer"&gt;Data Grid&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  What “collaborative” means
&lt;/h2&gt;

&lt;p&gt;In many products, collaboration only means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;sharing a link;&lt;/li&gt;
&lt;li&gt;leaving comments;&lt;/li&gt;
&lt;li&gt;exporting a file;&lt;/li&gt;
&lt;li&gt;sending a report to another user.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For a spreadsheet-like data grid, collaboration means something much deeper.&lt;/p&gt;

&lt;p&gt;A collaborative grid should allow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;multiple users to work in the same grid at the same time;&lt;/li&gt;
&lt;li&gt;real-time synchronization of edits;&lt;/li&gt;
&lt;li&gt;user presence indicators;&lt;/li&gt;
&lt;li&gt;visible selected cells and ranges from other users;&lt;/li&gt;
&lt;li&gt;conflict detection when two users edit the same data;&lt;/li&gt;
&lt;li&gt;conflict resolution policies;&lt;/li&gt;
&lt;li&gt;pending changes and review workflows;&lt;/li&gt;
&lt;li&gt;backend validation before final commit;&lt;/li&gt;
&lt;li&gt;audit history for accepted changes;&lt;/li&gt;
&lt;li&gt;rollback or restore when needed;&lt;/li&gt;
&lt;li&gt;permissions by user, role, row, column, or cell.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A basic editable grid asks:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Can this user edit this cell?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A collaborative grid asks:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Can this user edit this cell right now, while other users may also be editing related data, without losing work, breaking formulas, or violating permissions?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is a much harder problem.&lt;/p&gt;


&lt;h2&gt;
  
  
  Why collaborative spreadsheet could be hard to make?
&lt;/h2&gt;

&lt;p&gt;The UI looks simple.&lt;/p&gt;

&lt;p&gt;A user clicks a cell and changes a value:&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Budget Q3 = 120,000
&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;But a collaborative application has to understand much more:&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;CollaborativeCellChange&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;documentId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nl"&gt;previousValue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;nextValue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nl"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;clientId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;operationId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;Now imagine two users edit the same cell at nearly the same time.&lt;/p&gt;

&lt;p&gt;Anna changes the value from &lt;code&gt;120,000&lt;/code&gt; to &lt;code&gt;130,000&lt;/code&gt;.&lt;br&gt;
Mark changes the same value from &lt;code&gt;120,000&lt;/code&gt; to &lt;code&gt;125,000&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;How to resolve the conflict?&lt;/p&gt;

&lt;p&gt;There is no single correct answer.&lt;/p&gt;

&lt;p&gt;For a note field, the latest value may be fine.&lt;br&gt;
For a finance field, silent overwrite is dangerous.&lt;br&gt;
For a workflow status, one edit may need to be rejected.&lt;br&gt;
For a locked row, neither edit should be allowed.&lt;/p&gt;

&lt;p&gt;A real collaborative grid does not just send edits over WebSocket.&lt;/p&gt;

&lt;p&gt;It needs a clear product model.&lt;/p&gt;
&lt;h2&gt;
  
  
  Conflict resolution
&lt;/h2&gt;

&lt;p&gt;A conflict happens when two or more users edit the same data before the system can safely reconcile the changes.&lt;/p&gt;

&lt;p&gt;Example:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Anna changes &lt;code&gt;forecastMar&lt;/code&gt; from &lt;code&gt;18000&lt;/code&gt; to &lt;code&gt;20000&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Mark changes the same cell from &lt;code&gt;18000&lt;/code&gt; to &lt;code&gt;19000&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Both edits arrive close together.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The product must decide what to do.&lt;/p&gt;
&lt;h2&gt;
  
  
  Strategy 1: Manual conflict review
&lt;/h2&gt;

&lt;p&gt;Manual conflict review is the safest option for serious business data.&lt;/p&gt;

&lt;p&gt;The system keeps the local change pending and shows the user that another value already exists.&lt;/p&gt;

&lt;p&gt;Example UX:&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Conflict detected

Anna changed this cell to: 20,000
You changed it to: 19,000

Choose:
- Keep Anna's value
- Apply your value
- Open cell history
&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;This is useful for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;finance;&lt;/li&gt;
&lt;li&gt;resource planning;&lt;/li&gt;
&lt;li&gt;approvals;&lt;/li&gt;
&lt;li&gt;pricing;&lt;/li&gt;
&lt;li&gt;inventory;&lt;/li&gt;
&lt;li&gt;customer-facing data;&lt;/li&gt;
&lt;li&gt;operational workflows.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example editing configuration:&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;collaborativeEditing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;CollaborativeEditingConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
  &lt;span class="na"&gt;conflictPolicy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;manual&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;You can open a custom conflict dialog from the collaborative conflict event:&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;collaborativeconflict&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;CustomEvent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;conflict&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;detail&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nf"&gt;openConflictDialog&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;rowId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;conflict&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rowId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;columnId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;conflict&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;prop&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;localValue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;conflict&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;newValue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;remoteValue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;conflict&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;remoteValue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;remoteUser&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;conflict&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;remoteUser&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;Manual conflict resolution is usually the best default for high-value B2B workflows.&lt;/p&gt;

&lt;h2&gt;
  
  
  Strategy 2: Last write wins
&lt;/h2&gt;

&lt;p&gt;The simplest model is last write wins.&lt;/p&gt;

&lt;p&gt;The latest accepted edit becomes the final value.&lt;/p&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;collaborativeEditing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;CollaborativeEditingConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
  &lt;span class="na"&gt;conflictPolicy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;lastWriteWins&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;This can be acceptable for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;notes;&lt;/li&gt;
&lt;li&gt;temporary planning values;&lt;/li&gt;
&lt;li&gt;draft-only fields;&lt;/li&gt;
&lt;li&gt;low-risk comments;&lt;/li&gt;
&lt;li&gt;internal scratch data.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It is usually not appropriate for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;budgets;&lt;/li&gt;
&lt;li&gt;prices;&lt;/li&gt;
&lt;li&gt;approved values;&lt;/li&gt;
&lt;li&gt;inventory quantities;&lt;/li&gt;
&lt;li&gt;workflow status;&lt;/li&gt;
&lt;li&gt;financial records;&lt;/li&gt;
&lt;li&gt;compliance-sensitive data.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Last write wins feels smooth, but it can silently overwrite another user’s work.&lt;/p&gt;

&lt;p&gt;Use it deliberately.&lt;/p&gt;

&lt;h2&gt;
  
  
  Strategy 3: Soft warnings
&lt;/h2&gt;

&lt;p&gt;Sometimes the product should not block the user.&lt;/p&gt;

&lt;p&gt;It should warn them.&lt;/p&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight plaintext"&gt;&lt;code&gt;This value was changed by Anna 18 seconds ago.
Please review before saving.
&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;This is useful for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;planning grids;&lt;/li&gt;
&lt;li&gt;CRM updates;&lt;/li&gt;
&lt;li&gt;content calendars;&lt;/li&gt;
&lt;li&gt;lightweight operational data;&lt;/li&gt;
&lt;li&gt;provisional values.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Soft warnings reduce friction while still giving users context.&lt;/p&gt;

&lt;h2&gt;
  
  
  Strategy 4: Locks and protected ranges
&lt;/h2&gt;

&lt;p&gt;Some workflows should prevent conflicts before they happen.&lt;/p&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight plaintext"&gt;&lt;code&gt;This row is currently being edited by Anna.
&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;Or:&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight plaintext"&gt;&lt;code&gt;This month is locked because Finance already approved it.
&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;Locks are useful for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;approved periods;&lt;/li&gt;
&lt;li&gt;billing data;&lt;/li&gt;
&lt;li&gt;purchase orders;&lt;/li&gt;
&lt;li&gt;stock adjustments;&lt;/li&gt;
&lt;li&gt;workflow transitions;&lt;/li&gt;
&lt;li&gt;high-risk rows.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A lock can be strict:&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;RowLock&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;rowId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;lockedBy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;expiresAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;Or advisory:&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Anna is editing this row. You can continue, but your change may require review.
&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;For most real products, the best model is hybrid:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Data type&lt;/th&gt;
&lt;th&gt;Recommended strategy&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Notes and drafts&lt;/td&gt;
&lt;td&gt;Last write wins&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Normal business values&lt;/td&gt;
&lt;td&gt;Soft warning or manual review&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Finance, pricing, inventory&lt;/td&gt;
&lt;td&gt;Manual review&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Approved rows or closed periods&lt;/td&gt;
&lt;td&gt;Lock&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Formula cells&lt;/td&gt;
&lt;td&gt;Protected or review-only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Workflow status&lt;/td&gt;
&lt;td&gt;Permission-aware manual review&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;One global conflict strategy is rarely enough.&lt;/p&gt;

&lt;h2&gt;
  
  
  Presence: show who is working where
&lt;/h2&gt;

&lt;p&gt;Presence makes collaboration visible.&lt;/p&gt;

&lt;p&gt;A collaborative grid should show:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;who is currently viewing the document;&lt;/li&gt;
&lt;li&gt;who selected a cell;&lt;/li&gt;
&lt;li&gt;who selected a range;&lt;/li&gt;
&lt;li&gt;who is editing;&lt;/li&gt;
&lt;li&gt;which users are idle;&lt;/li&gt;
&lt;li&gt;when stale presence should disappear.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example UI:&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Anna is editing Forecast Mar
Mark selected B4:D8
Elena is viewing the Finance Review tab
&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;A useful presence model:&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;PresenceUser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;activity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;viewing&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;editing&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;idle&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nl"&gt;focus&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;rowId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;columnId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="nl"&gt;range&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;startRowId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;endRowId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;startColumnId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;endColumnId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="nl"&gt;lastActiveAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;Presence should be visual and temporary.&lt;/p&gt;

&lt;p&gt;It should not be treated as authoritative business data.&lt;/p&gt;

&lt;p&gt;Cell values are persistent.&lt;br&gt;
Presence is ephemeral.&lt;/p&gt;

&lt;p&gt;This distinction matters.&lt;/p&gt;
&lt;h2&gt;
  
  
  Permissions: collaboration must be safe
&lt;/h2&gt;

&lt;p&gt;In collaborative spreadsheets, permissions should not stop at page-level access.&lt;/p&gt;

&lt;p&gt;You may need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;document-level permissions;&lt;/li&gt;
&lt;li&gt;sheet-level permissions;&lt;/li&gt;
&lt;li&gt;row-level permissions;&lt;/li&gt;
&lt;li&gt;column-level permissions;&lt;/li&gt;
&lt;li&gt;cell-level permissions;&lt;/li&gt;
&lt;li&gt;formula permissions;&lt;/li&gt;
&lt;li&gt;export permissions;&lt;/li&gt;
&lt;li&gt;approval permissions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Role&lt;/th&gt;
&lt;th&gt;Can view&lt;/th&gt;
&lt;th&gt;Can edit&lt;/th&gt;
&lt;th&gt;Can approve&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Sales rep&lt;/td&gt;
&lt;td&gt;Own customers&lt;/td&gt;
&lt;td&gt;Own forecast cells&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sales manager&lt;/td&gt;
&lt;td&gt;Team customers&lt;/td&gt;
&lt;td&gt;Team forecast cells&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Finance&lt;/td&gt;
&lt;td&gt;All customers&lt;/td&gt;
&lt;td&gt;Approved values and formulas&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Executive&lt;/td&gt;
&lt;td&gt;Summary view&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The grid should show permissions visually.&lt;/p&gt;

&lt;p&gt;Good patterns:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;locked cells are muted;&lt;/li&gt;
&lt;li&gt;protected columns show a lock icon;&lt;/li&gt;
&lt;li&gt;context menu actions are hidden when unavailable;&lt;/li&gt;
&lt;li&gt;tooltips explain why a cell is read-only;&lt;/li&gt;
&lt;li&gt;rejected edits show clear reasons.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Bad UX:&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Error: forbidden
&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;Better UX:&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight plaintext"&gt;&lt;code&gt;This value is locked because March has already been approved by Finance.
&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;Frontend permissions improve the user experience, but backend permissions are still mandatory.&lt;/p&gt;

&lt;p&gt;Never trust only the browser.&lt;/p&gt;

&lt;h2&gt;
  
  
  Example: collaborative inventory planning grid
&lt;/h2&gt;

&lt;p&gt;A collaborative inventory planning app may have rows like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SKU;&lt;/li&gt;
&lt;li&gt;warehouse;&lt;/li&gt;
&lt;li&gt;current stock;&lt;/li&gt;
&lt;li&gt;reorder point;&lt;/li&gt;
&lt;li&gt;incoming quantity;&lt;/li&gt;
&lt;li&gt;expected delivery;&lt;/li&gt;
&lt;li&gt;supplier;&lt;/li&gt;
&lt;li&gt;risk level.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Users:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;warehouse operators;&lt;/li&gt;
&lt;li&gt;procurement managers;&lt;/li&gt;
&lt;li&gt;finance;&lt;/li&gt;
&lt;li&gt;supplier coordinators.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Grid rules:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;warehouse operators can edit stock counts;&lt;/li&gt;
&lt;li&gt;procurement can edit reorder quantities;&lt;/li&gt;
&lt;li&gt;finance can lock budget-sensitive fields;&lt;/li&gt;
&lt;li&gt;supplier coordinators can comment on delivery changes;&lt;/li&gt;
&lt;li&gt;high-risk SKUs are highlighted;&lt;/li&gt;
&lt;li&gt;every stock adjustment is audited.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is a good fit for a collaborative RevoGrid workflow because the interface needs spreadsheet speed, but the product needs business rules.&lt;/p&gt;

&lt;p&gt;A generic spreadsheet can store this data.&lt;/p&gt;

&lt;p&gt;A RevoGrid-based product can enforce the workflow.&lt;/p&gt;

&lt;h2&gt;
  
  
  Example: collaborative project planning grid
&lt;/h2&gt;

&lt;p&gt;A project planning grid may include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;project;&lt;/li&gt;
&lt;li&gt;task;&lt;/li&gt;
&lt;li&gt;owner;&lt;/li&gt;
&lt;li&gt;status;&lt;/li&gt;
&lt;li&gt;start date;&lt;/li&gt;
&lt;li&gt;end date;&lt;/li&gt;
&lt;li&gt;progress;&lt;/li&gt;
&lt;li&gt;dependency;&lt;/li&gt;
&lt;li&gt;risk;&lt;/li&gt;
&lt;li&gt;budget.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Users:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;project managers;&lt;/li&gt;
&lt;li&gt;resource managers;&lt;/li&gt;
&lt;li&gt;team leads;&lt;/li&gt;
&lt;li&gt;finance;&lt;/li&gt;
&lt;li&gt;client viewers.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Features needed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;inline editing;&lt;/li&gt;
&lt;li&gt;date editors;&lt;/li&gt;
&lt;li&gt;assignee dropdowns;&lt;/li&gt;
&lt;li&gt;status badges;&lt;/li&gt;
&lt;li&gt;comments;&lt;/li&gt;
&lt;li&gt;audit history;&lt;/li&gt;
&lt;li&gt;saved views;&lt;/li&gt;
&lt;li&gt;permissions;&lt;/li&gt;
&lt;li&gt;export to Excel;&lt;/li&gt;
&lt;li&gt;conflict handling;&lt;/li&gt;
&lt;li&gt;timeline or Gantt view.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is where &lt;a href="https://dev.to/pro/"&gt;RevoGrid Pro&lt;/a&gt; becomes interesting beyond the grid itself.&lt;/p&gt;

&lt;p&gt;A planning product may need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;grid for structured editing;&lt;/li&gt;
&lt;li&gt;formulas for calculations;&lt;/li&gt;
&lt;li&gt;audit history for accountability;&lt;/li&gt;
&lt;li&gt;Gantt for timelines;&lt;/li&gt;
&lt;li&gt;scheduler for resource allocation;&lt;/li&gt;
&lt;li&gt;pivot tables for reporting.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The grid becomes the center of a larger operational workflow.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to include in a production collaborative grid
&lt;/h2&gt;

&lt;p&gt;A serious collaborative spreadsheet should include the following.&lt;/p&gt;

&lt;h3&gt;
  
  
  Multi-user editing
&lt;/h3&gt;

&lt;p&gt;Users should be able to edit the same grid at the same time.&lt;/p&gt;

&lt;p&gt;Include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;live updates;&lt;/li&gt;
&lt;li&gt;optimistic edits;&lt;/li&gt;
&lt;li&gt;backend acknowledgement;&lt;/li&gt;
&lt;li&gt;pending state;&lt;/li&gt;
&lt;li&gt;rollback on rejection;&lt;/li&gt;
&lt;li&gt;conflict events;&lt;/li&gt;
&lt;li&gt;reconnect behavior.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Presence
&lt;/h3&gt;

&lt;p&gt;Users should see who else is active.&lt;/p&gt;

&lt;p&gt;Include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;user avatars or initials;&lt;/li&gt;
&lt;li&gt;active cell;&lt;/li&gt;
&lt;li&gt;selected range;&lt;/li&gt;
&lt;li&gt;editing label;&lt;/li&gt;
&lt;li&gt;stale user cleanup;&lt;/li&gt;
&lt;li&gt;subtle visual markers.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Conflict resolution
&lt;/h3&gt;

&lt;p&gt;Do not rely on one universal strategy.&lt;/p&gt;

&lt;p&gt;Include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;manual review for critical cells;&lt;/li&gt;
&lt;li&gt;last write wins for low-risk fields;&lt;/li&gt;
&lt;li&gt;soft warnings for normal business edits;&lt;/li&gt;
&lt;li&gt;locks for approved or transactional rows.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Permissions
&lt;/h3&gt;

&lt;p&gt;Permissions should be visible and enforced.&lt;/p&gt;

&lt;p&gt;Include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;read-only columns;&lt;/li&gt;
&lt;li&gt;row locks;&lt;/li&gt;
&lt;li&gt;cell-level edit rules;&lt;/li&gt;
&lt;li&gt;role-based permissions;&lt;/li&gt;
&lt;li&gt;server-side validation;&lt;/li&gt;
&lt;li&gt;clear rejection messages.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Audit history
&lt;/h3&gt;

&lt;p&gt;Every important change should be reviewable.&lt;/p&gt;

&lt;p&gt;Include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;who changed it;&lt;/li&gt;
&lt;li&gt;previous value;&lt;/li&gt;
&lt;li&gt;new value;&lt;/li&gt;
&lt;li&gt;timestamp;&lt;/li&gt;
&lt;li&gt;source of change;&lt;/li&gt;
&lt;li&gt;transaction grouping;&lt;/li&gt;
&lt;li&gt;restore action;&lt;/li&gt;
&lt;li&gt;exportable audit log.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Spreadsheet behavior
&lt;/h3&gt;

&lt;p&gt;Users expect spreadsheet-like interaction.&lt;/p&gt;

&lt;p&gt;Include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;formulas;&lt;/li&gt;
&lt;li&gt;copy and paste;&lt;/li&gt;
&lt;li&gt;range selection;&lt;/li&gt;
&lt;li&gt;multi-range selection;&lt;/li&gt;
&lt;li&gt;smart auto-fill;&lt;/li&gt;
&lt;li&gt;validation;&lt;/li&gt;
&lt;li&gt;merged cells where needed;&lt;/li&gt;
&lt;li&gt;Excel export/import.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Product workflow
&lt;/h3&gt;

&lt;p&gt;A collaborative grid should fit your domain.&lt;/p&gt;

&lt;p&gt;Include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;custom cell renderers;&lt;/li&gt;
&lt;li&gt;status badges;&lt;/li&gt;
&lt;li&gt;approval states;&lt;/li&gt;
&lt;li&gt;comments;&lt;/li&gt;
&lt;li&gt;saved views;&lt;/li&gt;
&lt;li&gt;tooltips;&lt;/li&gt;
&lt;li&gt;context menus;&lt;/li&gt;
&lt;li&gt;workflow actions.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Common mistakes
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Mistake 1: Treating WebSocket sync as collaboration
&lt;/h3&gt;

&lt;p&gt;A WebSocket moves messages.&lt;/p&gt;

&lt;p&gt;It does not solve:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;permissions;&lt;/li&gt;
&lt;li&gt;conflicts;&lt;/li&gt;
&lt;li&gt;validation;&lt;/li&gt;
&lt;li&gt;audit;&lt;/li&gt;
&lt;li&gt;rollback;&lt;/li&gt;
&lt;li&gt;formulas;&lt;/li&gt;
&lt;li&gt;reconnect;&lt;/li&gt;
&lt;li&gt;transaction grouping.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Real collaboration needs a product model.&lt;/p&gt;

&lt;h3&gt;
  
  
  Mistake 2: Using row indexes as identity
&lt;/h3&gt;

&lt;p&gt;Indexes change after sorting and filtering.&lt;/p&gt;

&lt;p&gt;Use stable row IDs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Mistake 3: Applying last write wins everywhere
&lt;/h3&gt;

&lt;p&gt;Last write wins is convenient, but dangerous.&lt;/p&gt;

&lt;p&gt;It is acceptable for low-risk values.&lt;br&gt;
It is not acceptable for budgets, approval states, inventory, pricing, or financial records.&lt;/p&gt;

&lt;h3&gt;
  
  
  Mistake 4: Replacing the whole grid source after every edit
&lt;/h3&gt;

&lt;p&gt;This can break:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;focus;&lt;/li&gt;
&lt;li&gt;selection;&lt;/li&gt;
&lt;li&gt;scroll position;&lt;/li&gt;
&lt;li&gt;plugin state;&lt;/li&gt;
&lt;li&gt;active editors;&lt;/li&gt;
&lt;li&gt;pending changes;&lt;/li&gt;
&lt;li&gt;undo history.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Use targeted updates where possible.&lt;/p&gt;

&lt;h3&gt;
  
  
  Mistake 5: Making filters shared by default
&lt;/h3&gt;

&lt;p&gt;Personal filters should stay personal.&lt;/p&gt;

&lt;p&gt;Shared views should be explicit.&lt;/p&gt;

&lt;h3&gt;
  
  
  Mistake 6: Ignoring bulk operations
&lt;/h3&gt;

&lt;p&gt;Paste, fill, and import are not edge cases.&lt;/p&gt;

&lt;p&gt;They are core spreadsheet workflows.&lt;/p&gt;

&lt;p&gt;Treat them as transactions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where this all fits
&lt;/h2&gt;

&lt;p&gt;We build you a &lt;a href="https://rv-grid.com" rel="noopener noreferrer"&gt;grid&lt;/a&gt; that gives you the high-performance spreadsheet-like grid surface:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;virtualized rows and columns;&lt;/li&gt;
&lt;li&gt;inline editing;&lt;/li&gt;
&lt;li&gt;keyboard navigation;&lt;/li&gt;
&lt;li&gt;range selection;&lt;/li&gt;
&lt;li&gt;copy and paste workflows;&lt;/li&gt;
&lt;li&gt;custom cell renderers;&lt;/li&gt;
&lt;li&gt;custom editors;&lt;/li&gt;
&lt;li&gt;column types;&lt;/li&gt;
&lt;li&gt;filtering and sorting;&lt;/li&gt;
&lt;li&gt;framework integrations for React, Vue, Angular, Svelte, and plain JavaScript.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The collaboration stack uses more than one package. &lt;a href="https://dev.to/pro/"&gt;RevoGrid Pro&lt;/a&gt; provides the grid-side helpers such as &lt;code&gt;EventManagerPlugin&lt;/code&gt; and &lt;code&gt;CollaborativePresencePlugin&lt;/code&gt;. Collaborative editing itself comes from the separate &lt;code&gt;@revolist/revogrid-collaborative-editing&lt;/code&gt; package.&lt;/p&gt;

&lt;p&gt;Together, they extend this foundation with advanced spreadsheet and workflow capabilities such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;normalized edit event management for collaborative write flows;&lt;/li&gt;
&lt;li&gt;collaborative editing through &lt;code&gt;@revolist/revogrid-collaborative-editing&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;collaborative presence markers for remote focus and range selection;&lt;/li&gt;
&lt;li&gt;conflict handling;&lt;/li&gt;
&lt;li&gt;audit trail history;&lt;/li&gt;
&lt;li&gt;undo and redo history;&lt;/li&gt;
&lt;li&gt;formulas;&lt;/li&gt;
&lt;li&gt;validation;&lt;/li&gt;
&lt;li&gt;range copy preview;&lt;/li&gt;
&lt;li&gt;smart auto-fill;&lt;/li&gt;
&lt;li&gt;multi-range selection;&lt;/li&gt;
&lt;li&gt;Excel import/export;&lt;/li&gt;
&lt;li&gt;advanced filters;&lt;/li&gt;
&lt;li&gt;pivot tables;&lt;/li&gt;
&lt;li&gt;Gantt and scheduler modules.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The important architectural point is simple:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;DataGrid should own the grid experience.&lt;br&gt;
Your application should own the business rules.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;RevoGrid handles the editing surface, rendering, events, selection, custom cells, and spreadsheet-like UX.&lt;/p&gt;

&lt;p&gt;Your application and backend handle:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;authentication;&lt;/li&gt;
&lt;li&gt;user roles;&lt;/li&gt;
&lt;li&gt;document access;&lt;/li&gt;
&lt;li&gt;row and column permissions;&lt;/li&gt;
&lt;li&gt;business validation;&lt;/li&gt;
&lt;li&gt;persistence;&lt;/li&gt;
&lt;li&gt;audit storage;&lt;/li&gt;
&lt;li&gt;conflict policy;&lt;/li&gt;
&lt;li&gt;final commit or rejection.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This separation keeps the system scalable and maintainable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final thought
&lt;/h2&gt;

&lt;p&gt;Collaborative spreadsheets are difficult because users expect them to feel simple.&lt;/p&gt;

&lt;p&gt;They want to edit, paste, filter, calculate, comment, undo, and collaborate without thinking about the system underneath.&lt;/p&gt;

&lt;p&gt;But the system underneath needs to handle:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;simultaneous edits;&lt;/li&gt;
&lt;li&gt;conflict resolution;&lt;/li&gt;
&lt;li&gt;permissions;&lt;/li&gt;
&lt;li&gt;audit history;&lt;/li&gt;
&lt;li&gt;validation;&lt;/li&gt;
&lt;li&gt;formulas;&lt;/li&gt;
&lt;li&gt;real-time updates;&lt;/li&gt;
&lt;li&gt;recovery;&lt;/li&gt;
&lt;li&gt;large datasets;&lt;/li&gt;
&lt;li&gt;business workflows.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The best approach is not to clone Google Sheets feature by feature.&lt;/p&gt;

&lt;p&gt;The better approach is to build a &lt;strong&gt;product-specific collaborative grid&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;One that feels familiar like a spreadsheet, but understands your data model, permissions, workflow, and business rules.&lt;/p&gt;

&lt;p&gt;That is where RevoGrid fits well.&lt;/p&gt;

&lt;p&gt;Use &lt;a href="https://rv-grid.com" rel="noopener noreferrer"&gt;RevoGrid&lt;/a&gt; for the high-performance spreadsheet-like grid surface.&lt;/p&gt;

&lt;p&gt;Use your backend for authority, validation, permissions, persistence, and audit.&lt;/p&gt;

&lt;p&gt;That combination gives you a practical path to building collaborative grid experiences that users can work in all day.&lt;/p&gt;

</description>
      <category>frontend</category>
      <category>javascript</category>
      <category>datagrid</category>
      <category>spreadsheet</category>
    </item>
    <item>
      <title>JavaScript Proxy: One more way to use it I wish I’d known 3 years ago</title>
      <dc:creator>Maks</dc:creator>
      <pubDate>Sun, 07 Jun 2026 19:29:52 +0000</pubDate>
      <link>https://dev.to/kumakint/javascript-proxy-one-more-way-to-use-it-i-wish-id-known-3-years-ago-40lk</link>
      <guid>https://dev.to/kumakint/javascript-proxy-one-more-way-to-use-it-i-wish-id-known-3-years-ago-40lk</guid>
      <description>&lt;p&gt;In our everyday work, we do a lot of data shuffling.&lt;/p&gt;

&lt;p&gt;We work with heavily loaded data grids. We have built our own &lt;a href="https://rv-grid.com/" rel="noopener noreferrer"&gt;RevoGrid&lt;/a&gt; for this matter hence market didn't offer all options we need back than. Some of our clients want to pull &lt;strong&gt;400k rows and 5k columns&lt;/strong&gt; into the UI and look at them on huge monitors.&lt;/p&gt;

&lt;p&gt;Don’t ask me what they see there. We deliver.&lt;/p&gt;

&lt;p&gt;For a very long time, our approach was to rely on different event management systems. Based on the event type, we would incrementally update data sources outside of the grid.&lt;/p&gt;

&lt;p&gt;And this works.&lt;/p&gt;

&lt;p&gt;But the problem with this approach is that you slowly end up with a lot of different events:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;cell edit&lt;/li&gt;
&lt;li&gt;range fill&lt;/li&gt;
&lt;li&gt;autofill&lt;/li&gt;
&lt;li&gt;copy/paste&lt;/li&gt;
&lt;li&gt;row grouping&lt;/li&gt;
&lt;li&gt;grouping migration&lt;/li&gt;
&lt;li&gt;column mapping&lt;/li&gt;
&lt;li&gt;source refresh&lt;/li&gt;
&lt;li&gt;external updates&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Trust me, a lot.&lt;/p&gt;

&lt;p&gt;So we were growing together with these events. One more feature, one more event. One more edge case, one more handler.&lt;/p&gt;

&lt;p&gt;But we live in the age of reactivity, so why not take one step further and make the source itself reactive?&lt;/p&gt;

&lt;p&gt;Instead of only reacting to &lt;a href="https://rv-grid.com/guide/api/events" rel="noopener noreferrer"&gt;grid&lt;/a&gt; events, we can feed the grid a source wrapped with &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy" rel="noopener noreferrer"&gt;&lt;code&gt;Proxy&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The idea is simple:&lt;/p&gt;

&lt;p&gt;The grid reads from the row through the &lt;code&gt;get&lt;/code&gt; trap.&lt;/p&gt;

&lt;p&gt;And when the grid tries to write something back into the data source, we catch that write through the &lt;code&gt;set&lt;/code&gt; trap.&lt;/p&gt;

&lt;p&gt;So the grid still works with normal-looking row objects:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;price&lt;/span&gt;
&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;quantity&lt;/span&gt;
&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But under the hood, we now control how reads and writes actually happen.&lt;/p&gt;

&lt;p&gt;A very simplified version looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;createRowProxy&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;object&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;onWrite&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;prop&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;keyof&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Proxy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;prop&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;receiver&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;Reflect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;prop&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;receiver&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;

    &lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;prop&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;receiver&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Reflect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;prop&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;receiver&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="nf"&gt;onWrite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;prop&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kr"&gt;keyof&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then we can pass proxied rows into the grid:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;rawRows&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
  &lt;span class="nf"&gt;createRowProxy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;prop&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;changed:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;prop&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// mark field as dirty&lt;/span&gt;
    &lt;span class="c1"&gt;// update external store&lt;/span&gt;
    &lt;span class="c1"&gt;// queue backend sync&lt;/span&gt;
    &lt;span class="c1"&gt;// trigger validation&lt;/span&gt;
    &lt;span class="c1"&gt;// update derived state&lt;/span&gt;
  &lt;span class="p"&gt;}),&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;source&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the interesting part:&lt;/p&gt;

&lt;p&gt;The grid/component does not need to know anything about your real store architecture.&lt;/p&gt;

&lt;p&gt;It does not care if your data lives in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a plain array&lt;/li&gt;
&lt;li&gt;a &lt;code&gt;Map&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;a normalized store&lt;/li&gt;
&lt;li&gt;a remote cache&lt;/li&gt;
&lt;li&gt;a local-first database&lt;/li&gt;
&lt;li&gt;a custom reactive state layer&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;From the grid perspective, it just reads and writes object properties.&lt;/p&gt;

&lt;p&gt;But from your application perspective, you now have a controlled bridge between the grid and your real data model.&lt;/p&gt;

&lt;p&gt;For example, the grid can read:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;quantity&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But internally this can be resolved from something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;rowsById&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;quantity&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same for writes.&lt;/p&gt;

&lt;p&gt;The grid can do:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;quantity&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And your proxy can decide what should happen next:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;prop&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;updateStore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;prop&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;markDirty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;prop&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;queueSave&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is where the pattern becomes useful.&lt;/p&gt;

&lt;p&gt;You stop treating every grid event as the main data pipeline.&lt;/p&gt;

&lt;p&gt;Instead, the row object becomes an adapter.&lt;/p&gt;

&lt;p&gt;Events are still useful. I would not remove them completely.&lt;/p&gt;

&lt;p&gt;You still want events for things like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;validation cancellation&lt;/li&gt;
&lt;li&gt;editor lifecycle&lt;/li&gt;
&lt;li&gt;analytics&lt;/li&gt;
&lt;li&gt;permissions&lt;/li&gt;
&lt;li&gt;before/after hooks&lt;/li&gt;
&lt;li&gt;custom UX behavior&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But for data synchronization itself, proxy-based source handling can be much cleaner.&lt;/p&gt;

&lt;p&gt;Especially when you have many ways to change data:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;direct cell editing&lt;/li&gt;
&lt;li&gt;paste&lt;/li&gt;
&lt;li&gt;autofill&lt;/li&gt;
&lt;li&gt;range updates&lt;/li&gt;
&lt;li&gt;programmatic updates&lt;/li&gt;
&lt;li&gt;grouping changes&lt;/li&gt;
&lt;li&gt;external source changes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instead of writing separate synchronization logic for every path, you can centralize a big part of it at the data access level.&lt;/p&gt;

&lt;p&gt;Of course, there are rules.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Do not create proxies inside render loops&lt;/li&gt;
&lt;li&gt;Keep proxy identity stable&lt;/li&gt;
&lt;li&gt;Avoid mixing too much logic in both edit events and proxy setters, otherwise you will double-handle the same update&lt;/li&gt;
&lt;li&gt;Think carefully about performance if you are working with very large datasets.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But as a pattern, it is surprisingly practical.&lt;/p&gt;

&lt;p&gt;For me, this changed how I think about data flow - component does not always need to own the data flow.&lt;/p&gt;

&lt;p&gt;Sometimes it is better to give it an object that looks simple from the outside, but is smart enough inside to route reads and writes into your actual application state.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I wrote this while working on editable data-grid internals. We use this pattern in &lt;a href="https://rv-grid.com/" rel="noopener noreferrer"&gt;RevoGrid’s&lt;/a&gt; proxy source approach, but the idea itself works with any editable table/grid where you want your app state to stay in control.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Happy coding!&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>architecture</category>
      <category>frontend</category>
    </item>
    <item>
      <title>React Data Tables Made Simple: A Friendly Guide into Data Grids</title>
      <dc:creator>Maks</dc:creator>
      <pubDate>Tue, 21 Jan 2025 21:08:28 +0000</pubDate>
      <link>https://dev.to/kumakint/react-data-tables-made-simple-a-friendly-guide-into-data-grids-4k9o</link>
      <guid>https://dev.to/kumakint/react-data-tables-made-simple-a-friendly-guide-into-data-grids-4k9o</guid>
      <description>&lt;p&gt;Data tables (or so called data grids) are at the core of many modern web applications, especially when dealing with large datasets or complex interactions. React's ecosystem offers several ways to implement a data grid, but today we'll compare two approaches:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A simple, custom-built React grid.&lt;/li&gt;
&lt;li&gt;A robust and highly performant implementation using external libraries.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's dive in!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft9tskn5m6c4u6dhe4zv7.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft9tskn5m6c4u6dhe4zv7.jpeg" alt=" " width="" height=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating a Simple React Grid
&lt;/h2&gt;

&lt;p&gt;Before jumping into advanced solutions, let’s start with a basic implementation of a data grid in React using mock data. This example will give you a feel for how React components, hooks, and states can work together to create a functional grid.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Code Example&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Mock data generator&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;generateMockData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;length&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;index&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Name &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;age&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;}));&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;SimpleGrid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;generateMockData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;thead&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;tr&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;th&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;ID&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/th&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;          &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;th&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Name&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/th&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;          &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;th&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Age&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/th&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/tr&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/thead&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;tbody&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;tr&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;td&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/td&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;td&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/td&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;td&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;age&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/td&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;          &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/tr&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;        &lt;span class="p"&gt;))}&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/tbody&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/table&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;SimpleGrid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Mock Data Generator: Creates sample rows with an id, name, and age for demonstration purposes.&lt;/li&gt;
&lt;li&gt;State Management: Uses useState to manage and render the mock data.&lt;/li&gt;
&lt;li&gt;HTML Table: Simple and styled to display the data as a grid.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So, you have an app that manages intensive datasets and you might think it’s straightforward enough to handle on your own. After all, implementing virtual scrolling might seem like all it takes.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;But what happens when your needs grow?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Suppose you want to &lt;a href="https://rv-grid.com/guide/defs.html#Row-Pin-Freeze" rel="noopener noreferrer"&gt;pin&lt;/a&gt; a row at the top or bottom. Add a column that also needs to be pinned. And then start grouping these elements. Soon, you might find yourself needing cell focus and &lt;a href="https://rv-grid.com/guide/defs.html#Range" rel="noopener noreferrer"&gt;range selections&lt;/a&gt;. This is where the complexity increases significantly.&lt;/p&gt;

&lt;p&gt;In scenarios like this, maintaining performance while adding sophisticated grid functionalities can become difficult.&lt;br&gt;
That’s where 3rd party components come in.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbx8kdpm09z8bgi94hpze.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbx8kdpm09z8bgi94hpze.jpg" alt=" " width="800" height="379"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  RevoGrid for Advanced Data Grids
&lt;/h2&gt;

&lt;p&gt;We decided to use &lt;a href="https://rv-grid.com" rel="noopener noreferrer"&gt;RevoGrid&lt;/a&gt; because we genuinely believe it’s the best data grid framework out there — and yes, we built it ourselves. With RevoGrid, you get React compatibility, unmatched performance, and the ability to handle massive datasets effortlessly. We’ve poured our expertise into creating a framework that’s feature-packed, highly customizable, and optimized to deliver a seamless experience. It’s the tool we’d choose for our own projects, and we’re confident you’ll love it too.&lt;/p&gt;
&lt;h3&gt;
  
  
  Installation
&lt;/h3&gt;

&lt;p&gt;To get started with RevoGrid, first install the library via npm:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; @revolist/react-datagrid
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Building a simple Example
&lt;/h2&gt;

&lt;p&gt;Creating a data grid with RevoGrid in React is remarkably simple and efficient. All you have to do is define your data model, and voilà!&lt;br&gt;
In the example below, we define a basic table with two columns — First and Second — and provide a static data source containing two rows.&lt;/p&gt;

&lt;p&gt;Using useMemo, we ensure that the column definitions maintain a stable reference to avoid unnecessary re-renders. Similarly, the source data is stored in state with useState.&lt;/p&gt;

&lt;p&gt;The  component then renders the grid, automatically handling the layout and performance optimizations for the provided configuration.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// App.tsx&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;RevoGrid&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@revolist/react-datagrid&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useMemo&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;App&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// note: columns &amp;amp; source need a "stable" reference&lt;/span&gt;
  &lt;span class="c1"&gt;// in order to prevent infinite re-renders&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;columns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useMemo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;prop&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;First&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;prop&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;details&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Second&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;[]&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;source&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;details&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Item 1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;details&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Item 2&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;]);&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;RevoGrid&lt;/span&gt; &lt;span class="nx"&gt;columns&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;columns&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;source&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;source&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;App&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://codesandbox.io/p/sandbox/react-revogrid-start-29fm5z?file=%2Fsrc%2FApp.tsx%3A10%2C22&amp;amp;from-embed=" rel="noopener noreferrer"&gt;Try it yourself!&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Building an advanced RevoGrid Example
&lt;/h2&gt;

&lt;p&gt;Below is an example implementation of RevoGrid in a React application. This example highlights row selection functionality and shows how to handle large datasets efficiently.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useMemo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useCallback&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useEffect&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ColumnRegular&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;RevoGrid&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@revolist/react-datagrid&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Mock data generator&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;generateFakeDataRows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rowsNumber&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;j&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;j&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;rowsNumber&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;j&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;j&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;j&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;myRow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Row &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;j&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// Make an App&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;App&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;generateFakeDataRows&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

  &lt;span class="c1"&gt;// Selection state management&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;selected&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setSelected&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;Set&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;allSelectedStatus&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setAllSelectedStatus&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;selectAll&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useCallback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;checked&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;setSelected&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;checked&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[]));&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;selectSingle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useCallback&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="na"&gt;rowId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;checked&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;setSelected&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;prevSelected&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;updatedSelected&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prevSelected&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;checked&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;updatedSelected&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rowId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;updatedSelected&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rowId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;updatedSelected&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt;

  &lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;selected&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;size&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;setAllSelectedStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;selected&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;size&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;setAllSelectedStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;setAllSelectedStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;selected&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="na"&gt;columns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ColumnRegular&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useMemo&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;allSelectedStatus&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;prop&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;myRow&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;columnTemplate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="nf"&gt;h&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;input&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;checkbox&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="na"&gt;indeterminate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="na"&gt;checked&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="nf"&gt;onChange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;InputEvent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nx"&gt;HTMLInputElement&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                  &lt;span class="nf"&gt;selectAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;checked&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
              &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="p"&gt;}),&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Select All&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="p"&gt;];&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="na"&gt;cellTemplate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;rowIndex&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;h&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;label&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="nf"&gt;h&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;input&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;checkbox&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;rowIndex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="na"&gt;checked&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;selected&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="nf"&gt;onChange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ChangeEvent&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;HTMLInputElement&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nx"&gt;HTMLInputElement&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                  &lt;span class="nf"&gt;selectSingle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;checked&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
              &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="p"&gt;}),&lt;/span&gt;
            &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="p"&gt;]);&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="na"&gt;size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;allSelectedStatus&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;selected&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;selectAll&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;selectSingle&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;RevoGrid&lt;/span&gt;
      &lt;span class="nx"&gt;columns&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;columns&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="nx"&gt;source&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="k"&gt;readonly&lt;/span&gt;
      &lt;span class="nx"&gt;theme&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;compact&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://codesandbox.io/p/sandbox/react-revogrid-multiselect-mgy263?file=%2Fsrc%2FApp.tsx" rel="noopener noreferrer"&gt;Try it yourself!&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For applications requiring high-performance grids, deep customization, and an excellent user experience, &lt;a href="https://rv-grid.com" rel="noopener noreferrer"&gt;RevoGrid&lt;/a&gt; is the perfect choice. Start building smarter, faster grids today!&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>react</category>
      <category>typescript</category>
    </item>
    <item>
      <title>Battle of the Rows: The Limits of Data Performance</title>
      <dc:creator>Maks</dc:creator>
      <pubDate>Tue, 19 Nov 2024 19:41:34 +0000</pubDate>
      <link>https://dev.to/kumakint/battle-of-the-rows-the-limits-of-data-performance-4mcn</link>
      <guid>https://dev.to/kumakint/battle-of-the-rows-the-limits-of-data-performance-4mcn</guid>
      <description>&lt;p&gt;Hi there! 😊 &lt;/p&gt;

&lt;p&gt;Welcome to our recently conducted series of performance benchmarks. Data grids are becoming more popular and increasingly important as they play a vital role in managing and visualizing large datasets, especially in data-intensive applications. In this article we are going to evaluate one of the most famous data grids: &lt;a href="https://www.ag-grid.com" rel="noopener noreferrer"&gt;AG Grid&lt;/a&gt;, &lt;a href="https://handsontable.com" rel="noopener noreferrer"&gt;Handsontable&lt;/a&gt;, and &lt;a href="https://rv-grid.com" rel="noopener noreferrer"&gt;RevoGrid&lt;/a&gt;. Our goal was to provide you with some insight into this rapidly growing field.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Benchmark Methodology&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We used the following metrics for our comparisons:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Memory Usage&lt;/strong&gt;: Measures how efficiently each grid uses memory (What could be more important in a heavily loaded app?).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scroll FPS&lt;/strong&gt;: Evaluates the smoothness of scrolling, using frames per second (FPS). Typically, 60 FPS is considered the standard for smooth scrolling, but actual FPS may vary depending on browser capabilities, device performance, and other environmental factors. Variations between tests can occur due to these differences in hardware and software conditions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Time Before First Cell Loaded&lt;/strong&gt;: How quickly a grid can render and display the first cell (or viewport) after data is loaded. &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Consider experimenting with the provided sample datasets. This hands-on approach will give you a practical understanding of grid performance.&lt;/p&gt;

&lt;p&gt;We used four different datasets of increasing size: 1,000 rows, 100,000 rows, 200,000 rows, and 400,000 rows, each containing 100 columns. The data was generated using a &lt;a href="https://github.com/revolist/revogrid-benchmarks/blob/main/src/columnGenerator.worker.ts" rel="noopener noreferrer"&gt;web worker&lt;/a&gt; to avoid environmental dependencies.&lt;/p&gt;

&lt;p&gt;We ran two types of tests:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Headless Mode&lt;/strong&gt;: Using the &lt;a href="https://playwright.dev" rel="noopener noreferrer"&gt;Playwright&lt;/a&gt; framework for objective, script-driven tests.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Visual Mode&lt;/strong&gt;: Testing manually in an actual browser environment.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Our goal was to create an unbiased, basic example of each grid, generating a large dataset and assessing how they handle this load under the same conditions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Results Overview Summary&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Handsontable
&lt;/h3&gt;

&lt;p&gt;The oldest among the three. It operates based on a two-dimensional array structure and relies on traditional HTML elements like &lt;code&gt;&amp;lt;table&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;tr&amp;gt;&lt;/code&gt;, and &lt;code&gt;&amp;lt;td&amp;gt;&lt;/code&gt;. This makes it less flexible and contributes to performance issues.&lt;/p&gt;

&lt;p&gt;Scrolling performance was subpar compared to the others; we noticed visible flickering when scrolling quickly, especially in dark mode. This may be due to Handsontable refreshing the entire viewport on each scroll event, which is common with traditional table structures.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;100,000 Rows&lt;/strong&gt;: 1297 MB of memory&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;200,000 Rows&lt;/strong&gt;: 2498.63 MB of memory&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Failed Beyond 300,000 Rows&lt;/strong&gt;: Due to memory overhead and table duplication for column freezing, Handsontable struggled beyond 300,000 rows, often leading to crashes.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  AG Grid
&lt;/h3&gt;

&lt;p&gt;The most widely-used grids, backed by heavy marketing efforts. It provides a solid, user-friendly experience and features smooth, native-like scrolling. However, it also showed high memory consumption, similar to Handsontable, with its memory usage growing exponentially as we increased the dataset size.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;100,000 Rows&lt;/strong&gt;: 1297 MB of memory&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;200,000 Rows&lt;/strong&gt;: 2660.75 MB of memory&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Failed Beyond 300,000 Rows&lt;/strong&gt;: Like Handsontable, AG Grid began to crash the browser tab when attempting to handle 400,000 rows.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  RevoGrid
&lt;/h3&gt;

&lt;p&gt;The youngest grid of the three, adopts a more modern approach. It uses virtual nodes backed by StencilJS, making it highly efficient for large datasets. RevoGrid includes advanced features that rival those of AG Grid, such as native smooth scrolling and customizable cells, while also being incredibly lightweight.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;200,000 Rows&lt;/strong&gt;: Only 368.12 MB of memory&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Succeeded at 400,000 Rows&lt;/strong&gt;: Handled 400,000 rows smoothly, using only 720 MB of RAM and retaining a smooth scroll experience.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Detailed Performance Metrics&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Below are the performance metrics we collected during testing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Testing 1,000 Rows

- AG Grid:
  - Used JS Heap Size: 40.15 MB
  - Scroll FPS: 60.23
  - Time to First Cell: 8.31 ms

- Handsontable:
  - Used JS Heap Size: 37.77 MB
  - Scroll FPS: 60.62
  - Time to First Cell: 6.67 ms

- RevoGrid:
  - Used JS Heap Size: 20.69 MB
  - Scroll FPS: 60.22
  - Time to First Cell: 6.07 ms

Testing 100,000 Rows

- **AG Grid**:
  - Used JS Heap Size: 1297.00 MB
  - Scroll FPS: 60.59
  - Time to First Cell: 10.95 ms

- Handsontable:
  - Used JS Heap Size: 1297.00 MB
  - Scroll FPS: 60.85
  - Time to First Cell: 16.71 ms

- RevoGrid:
  - Used JS Heap Size: 214.58 MB
  - Scroll FPS: 61.88
  - Time to First Cell: 6.09 ms

Testing 200,000 Rows

- AG Grid:
  - Used JS Heap Size: 2660.75 MB
  - Scroll FPS: 60.10
  - Time to First Cell: 7.42 ms

- Handsontable:
  - Used JS Heap Size: 2498.63 MB
  - Scroll FPS: 59.63
  - Time to First Cell: 13.10 ms

- RevoGrid:
  - Used JS Heap Size: 368.12 MB
  - Scroll FPS: 61.02
  - Time to First Cell: 6.14 ms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Visual Representations of Performance Metrics&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;To better illustrate the differences in performance between the three grids, we created bar charts for each key metric:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Memory Usage Comparison&lt;/strong&gt;: This chart shows how much memory each grid uses for different dataset sizes.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzu6eo0a3i5h9mxxlnsxt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzu6eo0a3i5h9mxxlnsxt.png" alt=" " width="800" height="510"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Time Before First Cell Loaded&lt;/strong&gt;: This chart highlights the time each grid takes to render and display the first cell. &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv7v812jf39glibscfbhl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv7v812jf39glibscfbhl.png" alt=" " width="" height=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Scroll FPS Comparison&lt;/strong&gt;: This chart visualizes the smoothness of scrolling, using frames per second as a measure. &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnk0kojxnfe7808bemk6h.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnk0kojxnfe7808bemk6h.png" alt=" " width="799" height="522"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Final thoughts&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The results of our testing clearly show that &lt;a href="https://rv-grid.com" rel="noopener noreferrer"&gt;RevoGrid&lt;/a&gt; outperforms the other grids in terms of memory efficiency, scalability, and overall performance. It effectively handles larger datasets with significantly lower memory usage while maintaining smooth scrolling and rapid load times.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.ag-grid.com" rel="noopener noreferrer"&gt;AG Grid&lt;/a&gt; provides a solid user experience, and &lt;a href="https://handsontable.com" rel="noopener noreferrer"&gt;Handsontable&lt;/a&gt; has some advantages in terms of familiarity and its traditional table semantics.&lt;/p&gt;

&lt;p&gt;If your project needs to manage vast amounts of data without sacrificing performance, RevoGrid is an excellent choice. Its lightweight, highly optimized approach ensures that you can scale without worrying about browser crashes or excessive memory use.&lt;/p&gt;

&lt;p&gt;Interested in comparing other grids like Tanstack, MUI, Syncfusion, or ReactTable? Let us know, and we'll create a detailed review for you.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/revolist/revogrid-benchmarks" rel="noopener noreferrer"&gt;Code repo&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>datagrid</category>
      <category>frontend</category>
      <category>performance</category>
    </item>
    <item>
      <title>How to get 2000+ Github stars in 2 days</title>
      <dc:creator>Maks</dc:creator>
      <pubDate>Mon, 25 Jan 2021 03:08:21 +0000</pubDate>
      <link>https://dev.to/kumakint/hot-to-get-400-github-stars-in-2-days-f8e</link>
      <guid>https://dev.to/kumakint/hot-to-get-400-github-stars-in-2-days-f8e</guid>
      <description>&lt;p&gt;Today our project &lt;a href="https://github.com/revolist/revogrid" rel="noopener noreferrer"&gt;https://github.com/revolist/revogrid&lt;/a&gt; received crossed 2700+ stars mark. And 2000 of them happened to us in the last 2 days.&lt;/p&gt;

&lt;p&gt;I think each of us came to GitHub with some goal. Most of us bound to the idea of organic promotion. According to it a good project is enough to become famous itself. But there are too many of them and we have to live with it.&lt;/p&gt;

&lt;p&gt;This is a very short instruction on how to get your first stars:&lt;/p&gt;

&lt;h2&gt;
  
  
  Solve my pain
&lt;/h2&gt;

&lt;p&gt;Write a project which the world has necessary in. You can't make people love it if there is no solution to their pain;&lt;/p&gt;

&lt;h2&gt;
  
  
  Good a Readme.
&lt;/h2&gt;

&lt;p&gt;This is the easiest part you can start with. You just have to learn from the best. I would start with &lt;a href="https://github.com/matiassingers/awesome-readme" rel="noopener noreferrer"&gt;https://github.com/matiassingers/awesome-readme&lt;/a&gt;, you can come with your own better sample;&lt;/p&gt;

&lt;h2&gt;
  
  
  Leave "cold stay zone"
&lt;/h2&gt;

&lt;p&gt;Get your first stars. Find a channel you like: Friends, colleagues, or some channel you start with.&lt;/p&gt;

&lt;h2&gt;
  
  
  And here we go
&lt;/h2&gt;

&lt;p&gt;The best way to launch your ship into space is to get visible by media.&lt;br&gt;
Best way to get into GitHub trending.  How to do that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Timing is important. If you aiming to be visible you have to get a significant amount of start in a short period of time;&lt;/li&gt;
&lt;li&gt; Describe your project for media: start with writing down a very simple and beautiful description. According to which even your granny can understand what it is. Follow it with a promo picture. Better have 2 &lt;/li&gt;
&lt;li&gt;Make sure you started your Twitter live and most of us like short news from each other;&lt;/li&gt;
&lt;li&gt;Start your activity on Reddit if you aren't there yet;&lt;/li&gt;
&lt;li&gt;Collect your channels in one place. You'll need them soon. I would suggest some, but discover your own too:&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.reddit.com/r/webdev" rel="noopener noreferrer"&gt;https://www.reddit.com/r/webdev&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.reddit.com/r/javascript" rel="noopener noreferrer"&gt;https://www.reddit.com/r/javascript&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.reddit.com/r/Frontend" rel="noopener noreferrer"&gt;https://www.reddit.com/r/Frontend&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.reddit.com/r/opensource" rel="noopener noreferrer"&gt;https://www.reddit.com/r/opensource&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.echojs.com" rel="noopener noreferrer"&gt;http://www.echojs.com&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://dev.to/t/javascript"&gt;https://dev.to/t/javascript&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Methodically start posting to your collected channels. Also, keep answering comments. This is very important, doesn't matter how harsh they are.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There is no clear mark where your project gets trended, for us, it was 160+ stars. But there were a lot of projects with 100+ stars and a smaller amount. The good thing is you'll get a lot of feedback about your project by this moment and will see different perspectives around it.&lt;/p&gt;

&lt;p&gt;I wish you luck and no matter what keep trying.&lt;/p&gt;

</description>
      <category>github</category>
      <category>webdev</category>
      <category>javascript</category>
      <category>opensource</category>
    </item>
    <item>
      <title>What are Protocol Buffers? Will they replace JSON?</title>
      <dc:creator>Maks</dc:creator>
      <pubDate>Sat, 23 Jan 2021 23:42:35 +0000</pubDate>
      <link>https://dev.to/kumakint/what-are-protocol-buffers-will-they-replace-json-3371</link>
      <guid>https://dev.to/kumakint/what-are-protocol-buffers-will-they-replace-json-3371</guid>
      <description>&lt;p&gt;&lt;a href="https://www.codewall.co.uk/what-are-protocol-buffers-will-they-replace-json/?utm_source=CWTwitter&amp;amp;utm_medium=social#" rel="noopener noreferrer"&gt;https://www.codewall.co.uk/what-are-protocol-buffers-will-they-replace-json/?utm_source=CWTwitter&amp;amp;utm_medium=social#&lt;/a&gt;&lt;/p&gt;

</description>
      <category>api</category>
      <category>frontend</category>
      <category>javascript</category>
      <category>vue</category>
    </item>
    <item>
      <title>Open source excel grid system with advanced performance for Vue, React, Ember, Angular, Svelte</title>
      <dc:creator>Maks</dc:creator>
      <pubDate>Fri, 22 Jan 2021 04:39:14 +0000</pubDate>
      <link>https://dev.to/kumakint/open-source-excel-grid-system-performance-beyond-expectations-and-works-across-all-frameworks-fo3</link>
      <guid>https://dev.to/kumakint/open-source-excel-grid-system-performance-beyond-expectations-and-works-across-all-frameworks-fo3</guid>
      <description>&lt;p&gt;On regular bases I’m working in development and consulting for project with complex frontend. Most of them require enormous data representation without problems for client, others require complex ui design systems.&lt;/p&gt;

&lt;p&gt;In the end revo-grid system was born. I have created data grid system which combines best excel sheet qualities with high performance.&lt;/p&gt;

&lt;p&gt;These are key features:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;VNode based DOM almost without rerendering. I do recombination instead;&lt;/li&gt;
&lt;li&gt;Full customization: custom cells, headers, properties, editors;&lt;/li&gt;
&lt;li&gt;Advanced plugin system with most important plugins out of box such as sorting, filtering, exporting to excel, column based on context size;&lt;/li&gt;
&lt;li&gt;Custom sizes for rows and columns;&lt;/li&gt;
&lt;li&gt;Column/Row grouping;&lt;/li&gt;
&lt;li&gt;Freezed columns/rows;&lt;/li&gt;
&lt;li&gt;Integration with all popular frameworks: react, vue, angulat, ember, vanilla;&lt;/li&gt;
&lt;li&gt;Multiple other useful features just to get it in use out of box.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Hope you will enjoy component. Looking for some feedback from you. Any suggestion are welcome. Thank you.&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>react</category>
      <category>vue</category>
      <category>excel</category>
    </item>
    <item>
      <title>Data grid with excel like features in 2025</title>
      <dc:creator>Maks</dc:creator>
      <pubDate>Thu, 01 Oct 2020 08:44:08 +0000</pubDate>
      <link>https://dev.to/kumakint/data-grid-with-excel-like-features-123p</link>
      <guid>https://dev.to/kumakint/data-grid-with-excel-like-features-123p</guid>
      <description>&lt;p&gt;In our company we are facing problems with huge datasets on client side. &lt;/p&gt;

&lt;p&gt;So far there wasn't any good solution with MIT license, we've decided to create our own (wish it would be that easy): &lt;a href="https://rv-grid.com" rel="noopener noreferrer"&gt;https://rv-grid.com&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;The biggest challenge was to create multiple viewport renderers which will work fast and extend easily.&lt;/p&gt;

&lt;p&gt;Except of usual virtualization we are using VNode DOM, which updates only changed parts, but we went much farther: - We are monitoring nodes per scroll and move nodes from edges, not entire screen redraw. With this we achieved amazing performance. Now we are going to add more features and grow up.&lt;/p&gt;

&lt;p&gt;Thank you for reading, hope to get some feedback from you guys.&lt;/p&gt;

</description>
      <category>datagrid</category>
      <category>vue</category>
      <category>react</category>
      <category>webcomponents</category>
    </item>
  </channel>
</rss>
