From 6ae8933084c65f6af86d80537aa862dda87b6e1f Mon Sep 17 00:00:00 2001 From: Daniel Lu Date: Wed, 7 May 2025 15:15:19 -0700 Subject: [PATCH 1/2] sample story with performance bug --- .../stories/Table.stories.tsx | 457 +++++++++++++++++- 1 file changed, 455 insertions(+), 2 deletions(-) diff --git a/packages/react-aria-components/stories/Table.stories.tsx b/packages/react-aria-components/stories/Table.stories.tsx index 553f46be57e..8ac773a98b3 100644 --- a/packages/react-aria-components/stories/Table.stories.tsx +++ b/packages/react-aria-components/stories/Table.stories.tsx @@ -11,10 +11,10 @@ */ import {action} from '@storybook/addon-actions'; -import {Button, Cell, Checkbox, CheckboxProps, Collection, Column, ColumnProps, ColumnResizer, Dialog, DialogTrigger, DropIndicator, Heading, Menu, MenuTrigger, Modal, ModalOverlay, Popover, ResizableTableContainer, Row, Table, TableBody, TableHeader, TableLayout, useDragAndDrop, Virtualizer} from 'react-aria-components'; +import {Button, Cell, Checkbox, CheckboxProps, Collection, Column, ColumnProps, ColumnResizer, Dialog, DialogTrigger, DropIndicator, Group, Heading, Menu, MenuTrigger, Modal, ModalOverlay, Popover, ResizableTableContainer, Row, Table, TableBody, TableHeader, TableLayout, useDragAndDrop, Virtualizer} from 'react-aria-components'; import {isTextDropItem} from 'react-aria'; import {MyMenuItem} from './utils'; -import React, {Suspense, useMemo, useRef, useState} from 'react'; +import React, {memo, startTransition, Suspense, useEffect, useRef, useState} from 'react'; import styles from '../example/index.css'; import {UNSTABLE_TableLoadingIndicator} from '../src/Table'; import {useAsyncList, useListData} from 'react-stately'; @@ -1084,3 +1084,456 @@ export const TableWithSuspense = { } } }; + +let rows1 = [ + { + id: 25, + name: 'Web Development', + date: '7/10/2023', + type: 'File folder' + }, + { + id: 26, + name: 'drivers', + date: '2/2/2022', + type: 'System file' + }, + { + id: 27, + name: 'debug.txt', + date: '12/5/2024', + type: 'Text Document' + }, + { + id: 28, + name: 'Marketing Plan.pptx', + date: '3/15/2025', + type: 'PowerPoint file' + }, + { + id: 29, + name: 'Contract_v3.pdf', + date: '1/2/2025', + type: 'PDF Document' + }, + { + id: 30, + name: 'Movies', + date: '5/20/2024', + type: 'File folder' + }, + { + id: 31, + name: 'User Manual.docx', + date: '9/1/2024', + type: 'Word Document' + }, + { + id: 32, + name: 'Sales Data_Q1.xlsx', + date: '4/10/2025', + type: 'Excel file' + }, + { + id: 33, + name: 'archive_old.rar', + date: '6/1/2023', + type: 'RAR archive' + }, + { + id: 34, + name: 'logo.svg', + date: '11/22/2024', + type: 'SVG image' + }, + { + id: 35, + name: 'main.py', + date: '10/1/2024', + type: 'Python file' + }, + { + id: 36, + name: 'base.html', + date: '8/18/2024', + type: 'HTML file' + }, + { + id: 37, + name: 'Configurations', + date: '4/5/2024', + type: 'File folder' + }, + { + id: 38, + name: 'kernel32.dll', + date: '9/10/2018', + type: 'System file' + }, + { + id: 39, + name: 'security_log.txt', + date: '3/28/2025', + type: 'Text Document' + }, + { + id: 40, + name: 'Project Proposal v2.pptx', + date: '1/15/2025', + type: 'PowerPoint file' + }, + { + id: 41, + name: 'NDA_Signed.pdf', + date: '12/20/2024', + type: 'PDF Document' + }, + { + id: 42, + name: 'Downloads', + date: '7/1/2024', + type: 'File folder' + }, + { + id: 43, + name: 'Meeting Minutes.docx', + date: '4/12/2025', + type: 'Word Document' + }, + { + id: 44, + name: 'Financial Report_FY24.xlsx', + date: '3/5/2025', + type: 'Excel file' + }, + { + id: 45, + name: 'data_backup_v1.tar.gz', + date: '11/8/2024', + type: 'GZIP archive' + }, + { + id: 46, + name: 'icon.ico', + date: '6/25/2024', + type: 'ICO file' + }, + { + id: 47, + name: 'app.config', + date: '9/30/2024', + type: 'Configuration file' + }, + { + id: 48, + name: 'Templates', + date: '2/10/2025', + type: 'File folder' + }, +]; + +let rows2 = [ + { + id: 100, + name: 'Assets', + date: '8/15/2024', + type: 'File folder' + }, + { + id: 101, + name: 'drivers64', + date: '3/3/2023', + type: 'System file' + }, + { + id: 102, + name: 'install.log', + date: '1/8/2025', + type: 'Text Document' + }, + { + id: 103, + name: 'Product Demo.pptx', + date: '4/20/2025', + type: 'PowerPoint file' + }, + { + id: 104, + name: 'Terms_of_Service.pdf', + date: '2/5/2025', + type: 'PDF Document' + }, + { + id: 105, + name: 'Animations', + date: '6/25/2024', + type: 'File folder' + }, + { + id: 106, + name: 'Release Notes.docx', + date: '10/1/2024', + type: 'Word Document' + }, + { + id: 107, + name: 'Financial Projections.xlsx', + date: '5/12/2025', + type: 'Excel file' + }, + { + id: 108, + name: 'backup_2023.tar', + date: '7/1/2024', + type: 'TAR archive' + }, + { + id: 109, + name: 'thumbnail.jpg', + date: '12/1/2024', + type: 'JPEG image' + }, + { + id: 110, + name: 'api_client.py', + date: '11/15/2024', + type: 'Python file' + }, + { + id: 111, + name: 'index.html', + date: '9/28/2024', + type: 'HTML file' + }, + { + id: 112, + name: 'Resources', + date: '5/5/2024', + type: 'File folder' + }, + { + id: 113, + name: 'msvcr100.dll', + date: '10/10/2019', + type: 'System file' + }, + { + id: 114, + name: 'system_events.txt', + date: '4/1/2025', + type: 'Text Document' + }, + { + id: 115, + name: 'Training Presentation.pptx', + date: '2/20/2025', + type: 'PowerPoint file' + }, + { + id: 116, + name: 'Privacy_Policy.pdf', + date: '1/10/2025', + type: 'PDF Document' + }, + { + id: 117, + name: 'Desktop', + date: '8/1/2024', + type: 'File folder' + }, + { + id: 118, + name: 'Meeting Agenda.docx', + date: '5/15/2025', + type: 'Word Document' + }, + { + id: 119, + name: 'Budget_Forecast.xlsx', + date: '4/15/2025', + type: 'Excel file' + }, + { + id: 120, + name: 'code_backup.7z', + date: '12/1/2024', + type: '7Z archive' + }, + { + id: 121, + name: 'icon_large.ico', + date: '7/1/2024', + type: 'ICO file' + }, + { + id: 122, + name: 'settings.ini', + date: '10/5/2024', + type: 'Configuration file' + }, + { + id: 123, + name: 'Project Docs', + date: '3/1/2025', + type: 'File folder' + }, + { + id: 124, + name: 'winload.exe', + date: '11/1/2010', + type: 'System file' + }, + { + id: 125, + name: 'application.log', + date: '6/1/2025', + type: 'Text Document' + }, + { + id: 126, + name: 'Client Presentation.pptx', + date: '3/1/2025', + type: 'PowerPoint file' + }, + { + id: 127, + name: 'EULA.pdf', + date: '2/15/2025', + type: 'PDF Document' + }, + { + id: 128, + name: 'Temporary', + date: '9/1/2024', + type: 'File folder' + }, + { + id: 129, + name: 'Action Items.docx', + date: '6/1/2025', + type: 'Word Document' + }, + { + id: 130, + name: 'Revenue_Report.xlsx', + date: '5/20/2025', + type: 'Excel file' + }, + { + id: 131, + name: 'data_dump.sql', + date: '1/1/2025', + type: 'SQL Dump' + }, + { + id: 132, + name: 'image_preview.bmp', + date: '8/20/2024', + type: 'Bitmap image' + }, + { + id: 133, + name: 'server.conf', + date: '11/20/2024', + type: 'Configuration file' + }, + { + id: 134, + name: 'Documentation', + date: '4/1/2025', + type: 'File folder' + }, + { + id: 135, + name: 'hal.dll', + date: '12/25/2007', + type: 'System file' + }, + { + id: 136, + name: 'access.log', + date: '7/1/2025', + type: 'Text Document' + }, + { + id: 137, + name: 'Strategy Presentation.pptx', + date: '4/1/2025', + type: 'PowerPoint file' + }, + { + id: 138, + name: 'Service Agreement.pdf', + date: '3/1/2025', + type: 'PDF Document' + }, + { + id: 139, + name: 'Recycle Bin', + date: '1/1/2000', + type: 'System folder' + }, +]; + +const columns1 = [ + { + id: 'name', + name: 'Name', + isRowHeader: true, + allowsSorting: true + }, + { + id: 'type', + name: 'Type', + allowsSorting: true + }, + { + id: 'date', + name: 'Date Modified', + allowsSorting: true + }, +]; + +export function TableTransitionSuspenceBug() { + const [show, setShow] = useState(true); + const items = show ? rows2 : rows1; + + return ( + <> + + + + ); +} + +const MemoTable = memo(({data}: { data: any }) => { + const ref = useRef(0); + useEffect(() => { + console.log('render count', ref.current++); + }); + + return ( +
+ + + {(column) => {column.name}} + + + {(row: any) => ( + + {/* @ts-ignore */} + {(column) => {row[column.id]}} + + )} + +
+
+ ); +}); From 4cec6a2cd318dd47157ea07c5fbdcd2f7947f57c Mon Sep 17 00:00:00 2001 From: Devon Govett Date: Wed, 7 May 2025 20:29:29 -0700 Subject: [PATCH 2/2] skip updating collection for disconnected nodes --- .../@react-aria/collections/src/Document.ts | 16 ++++++--- .../stories/Table.stories.tsx | 33 ++++++------------- 2 files changed, 22 insertions(+), 27 deletions(-) diff --git a/packages/@react-aria/collections/src/Document.ts b/packages/@react-aria/collections/src/Document.ts index 1385e18a413..23717073b14 100644 --- a/packages/@react-aria/collections/src/Document.ts +++ b/packages/@react-aria/collections/src/Document.ts @@ -140,7 +140,9 @@ export class BaseNode { this.lastChild = child; this.ownerDocument.markDirty(this); - this.ownerDocument.queueUpdate(); + if (this.isConnected) { + this.ownerDocument.queueUpdate(); + } } insertBefore(newNode: ElementNode, referenceNode: ElementNode): void { @@ -166,7 +168,9 @@ export class BaseNode { newNode.parentNode = referenceNode.parentNode; this.invalidateChildIndices(referenceNode); - this.ownerDocument.queueUpdate(); + if (this.isConnected) { + this.ownerDocument.queueUpdate(); + } } removeChild(child: ElementNode): void { @@ -197,7 +201,9 @@ export class BaseNode { child.index = 0; this.ownerDocument.markDirty(child); - this.ownerDocument.queueUpdate(); + if (this.isConnected) { + this.ownerDocument.queueUpdate(); + } } addEventListener(): void {} @@ -328,7 +334,9 @@ export class ElementNode extends BaseNode { } this.hasSetProps = true; - this.ownerDocument.queueUpdate(); + if (this.isConnected) { + this.ownerDocument.queueUpdate(); + } } get style(): CSSProperties { diff --git a/packages/react-aria-components/stories/Table.stories.tsx b/packages/react-aria-components/stories/Table.stories.tsx index 8ac773a98b3..28f110b5482 100644 --- a/packages/react-aria-components/stories/Table.stories.tsx +++ b/packages/react-aria-components/stories/Table.stories.tsx @@ -11,10 +11,10 @@ */ import {action} from '@storybook/addon-actions'; -import {Button, Cell, Checkbox, CheckboxProps, Collection, Column, ColumnProps, ColumnResizer, Dialog, DialogTrigger, DropIndicator, Group, Heading, Menu, MenuTrigger, Modal, ModalOverlay, Popover, ResizableTableContainer, Row, Table, TableBody, TableHeader, TableLayout, useDragAndDrop, Virtualizer} from 'react-aria-components'; +import {Button, Cell, Checkbox, CheckboxProps, Collection, Column, ColumnProps, ColumnResizer, Dialog, DialogTrigger, DropIndicator, Heading, Menu, MenuTrigger, Modal, ModalOverlay, Popover, ResizableTableContainer, Row, Table, TableBody, TableHeader, TableLayout, useDragAndDrop, Virtualizer} from 'react-aria-components'; import {isTextDropItem} from 'react-aria'; import {MyMenuItem} from './utils'; -import React, {memo, startTransition, Suspense, useEffect, useRef, useState} from 'react'; +import React, {startTransition, Suspense, useMemo, useRef, useState} from 'react'; import styles from '../example/index.css'; import {UNSTABLE_TableLoadingIndicator} from '../src/Table'; import {useAsyncList, useListData} from 'react-stately'; @@ -1229,7 +1229,7 @@ let rows1 = [ name: 'Templates', date: '2/10/2025', type: 'File folder' - }, + } ]; let rows2 = [ @@ -1472,7 +1472,7 @@ let rows2 = [ name: 'Recycle Bin', date: '1/1/2000', type: 'System folder' - }, + } ]; const columns1 = [ @@ -1491,41 +1491,28 @@ const columns1 = [ id: 'date', name: 'Date Modified', allowsSorting: true - }, + } ]; -export function TableTransitionSuspenceBug() { +export function TableWithReactTransition() { const [show, setShow] = useState(true); const items = show ? rows2 : rows1; return ( - <> +
- - - ); -} - -const MemoTable = memo(({data}: { data: any }) => { - const ref = useRef(0); - useEffect(() => { - console.log('render count', ref.current++); - }); - - return ( -
{(column) => {column.name}} - + {(row: any) => ( {/* @ts-ignore */} @@ -1536,4 +1523,4 @@ const MemoTable = memo(({data}: { data: any }) => {
); -}); +}