<?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>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>
