-
Notifications
You must be signed in to change notification settings - Fork 3.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
chore: refactor table widget UI code to use central context to reduce props drilling #39367
base: release
Are you sure you want to change the base?
chore: refactor table widget UI code to use central context to reduce props drilling #39367
Conversation
…management - Extracts table header logic into separate components - Introduces TableProvider context to manage table state - Moves types to separate file for better organization - Adds default compact mode fallback - Simplifies imports and reorganizes code structure
- Moves table state management to TableContext - Adds memoization for computed values - Extracts scroll container styles to context - Simplifies StaticTable component props - Adds SCROLL_BAR_OFFSET constant
- Removes redundant SimpleBar wrapper in Table component - Adds SimpleBar directly in TableHeader for consistent scrolling - Imports SimpleBar CSS for proper styling
…ering - Refactors VirtualTable to use TableContext for props - Simplifies Table component's conditional rendering - Removes redundant prop passing in VirtualTable - Adds type definition for StaticTable props
- Refactors HeaderCell to use TableContext for accessing shared props - Removes redundant prop passing in HeaderCell - Simplifies header cell rendering and interaction logic - Improves component readability and reduces prop drilling
- Removes explicit prop passing in TableColumnHeader - Uses TableContext to access shared table state - Calculates header width using useMemo - Simplifies header rendering and interaction logic
- Refactors renderEmptyRows to use TableContext for accessing table state - Removes explicit prop passing in EmptyRows and EmptyRow components - Simplifies empty row rendering logic - Improves component readability and reduces prop drilling
WalkthroughThis pull request introduces several enhancements and refactors within the TableWidgetV2 code. A new constant has been added, and outdated components (such as the earlier StaticTable, TableBody, and VirtualTable variants) have been removed. New components have been implemented for both static and virtual table bodies, along with a dedicated TableContext to centralize state management via the Changes
Sequence Diagram(s)sequenceDiagram
participant TP as TableProvider (useAppsmithTable)
participant VT as VirtualTable
participant ISB as InfiniteScrollBody
participant VTBC as VirtualTableBodyComponent
participant FV as FixedVirtualList
User->>TP: Initialize table state
VT->>TP: Retrieve table properties via hook
alt Infinite Scroll Enabled
VT->>ISB: Render infinite scroll body
ISB->>FV: Use fixed virtual list to render rows
else
VT->>VTBC: Render virtual table body component
VTBC->>FV: Use fixed virtual list to render rows
end
Possibly related PRs
Suggested labels
Suggested reviewers
Poem
✨ Finishing Touches
Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media? 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
Documentation and Community
|
…o rahulbarwal/tablev2-table-ui-components-refactor
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
🧹 Nitpick comments (16)
app/client/src/widgets/TableWidgetV2/component/header/index.tsx (3)
13-49
: Refactor for Enhanced ReadabilityThe direct extraction of many properties from
useAppsmithTable()
can become unwieldy. Consider grouping related properties into sub-objects to improve clarity and reduce repetitive prop spreads throughout the code.
59-77
: Check Scrolling BehaviorWrapping with
SimpleBar
is a neat approach to manage scrolling. Validate that scroll events and styles are correctly handled in all browsers. A fallback for environments without custom scrollbar support could further improve UX.
78-112
: Consider Bundling Props
BannerNActions
receives numerous props, which might be streamlined using an object or a context. This can help reduce repetitive passing of props and improve maintainability.app/client/src/widgets/TableWidgetV2/component/header/BannerNActions.tsx (2)
7-16
: Leverage TypeScript Union TypesExplicitly defining a union type for scenarios like
isAddRowInProgress
can create clearer, narrower prop contracts. Consider applying such patterns to better communicate the intended usage.
18-34
: Centralize Conditional LogicConditional rendering is handled cleanly. If more states and banner types emerge in the future, moving conditional logic into a dedicated helper function or a switch-based approach may improve scalability.
app/client/src/widgets/TableWidgetV2/component/cellComponents/HeaderCell.tsx (2)
177-178
: Offset logic for sticky column with multi-row selection.
Ensureprops.column.totalLeft
is always a valid numeric value to avoid misaligned headers.
204-205
: Optional chaining to handle freeze.
IfhandleColumnFreeze
will always be defined from the hook, consider removing the guard.🧰 Tools
🪛 Biome (1.9.4)
[error] 204-208: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
app/client/src/widgets/TableWidgetV2/component/Table.tsx (1)
133-136
: Confirm behavior for out-of-range pageNo.
Ifprops.pageNo
exceedspageCount
, resetting to 0 may be unexpected. Consider clamping to the last page instead if that's the desired user experience.app/client/src/widgets/TableWidgetV2/component/StaticTable/index.tsx (1)
8-8
: Use a type alias instead of an empty interface.
Empty interfaces are equivalent to{}
. Updating with a type alias improves clarity:-interface StaticTableProps {} +type StaticTableProps = Record<string, never>;🧰 Tools
🪛 Biome (1.9.4)
[error] 7-8: An empty interface is equivalent to {}.
Safe fix: Use a type alias instead.
(lint/suspicious/noEmptyInterface)
app/client/src/widgets/TableWidgetV2/component/VirtualTable/InifiniteScrollBody/index.tsx (1)
36-36
: Extract magic number into a named constant.The buffer size of 5 should be defined as a named constant to improve code maintainability and clarity.
+const ITEM_COUNT_BUFFER = 5; - itemCount={itemCount + 5} + itemCount={itemCount + ITEM_COUNT_BUFFER}app/client/src/widgets/TableWidgetV2/component/TableContext.tsx (2)
76-84
: Update error message to match hook name.The error message refers to "useTable" but the hook is named "useAppsmithTable".
- throw new Error("useTable must be used within a TableProvider"); + throw new Error("useAppsmithTable must be used within a TableProvider");
54-65
: Optimize memoization dependencies.The
scrollContainerStyles
memo dependency onstate.compactMode
is unnecessary sincetableSizes
is already derived from it.}, [ state.isHeaderVisible, state.height, - state.compactMode, state.width ]);
app/client/src/widgets/TableWidgetV2/component/TableBodyCoreComponents/Row.tsx (2)
67-99
: Extract cell rendering logic to a separate component.The cell rendering logic is complex and could benefit from being extracted into a separate component for better maintainability and potential reuse.
+ const TableCell = React.memo(({ cell, cellIndex, columns, multiRowSelection }) => { + const cellProperties = cell.getCellProps(); + cellProperties["style"] = { + ...cellProperties.style, + left: + columns[cellIndex].sticky === StickyType.LEFT && multiRowSelection + ? cell.column.totalLeft + MULTISELECT_CHECKBOX_WIDTH + : cellProperties?.style?.left, + }; + return ( + <div + {...cellProperties} + className={/* ... existing className logic ... */} + data-colindex={cellIndex} + data-rowindex={props.index} + key={cellIndex} + > + {cell.render("Cell")} + </div> + ); + });
32-38
: Memoize row style computation.The row style computation could be memoized to prevent unnecessary recalculations.
+ const rowStyle = React.useMemo( + () => ({ + display: "flex", + ...(props.style || {}), + }), + [props.style] + ); const rowProps = { ...props.row.getRowProps(), - style: { - display: "flex", - ...(props.style || {}), - }, + style: rowStyle, };app/client/src/widgets/TableWidgetV2/component/header/TableColumnHeader.tsx (1)
113-113
: Add error boundary for empty rows renderingConsider wrapping the empty rows rendering with an error boundary to gracefully handle potential rendering failures.
app/client/src/widgets/TableWidgetV2/component/cellComponents/EmptyCell.tsx (1)
127-154
: Optimize sticky column calculationsConsider memoizing the sticky column calculations to prevent unnecessary recalculations during re-renders.
+const calculateLeftDistance = React.useMemo(() => { const leftColWidths = tableColumns .slice(0, colIndex) .map((col) => col.width); if (multiRowSelection) { return colIndex === 0 ? MULTISELECT_CHECKBOX_WIDTH : sum(leftColWidths) + MULTISELECT_CHECKBOX_WIDTH; } return colIndex === 0 ? 0 : sum(leftColWidths); +}, [colIndex, multiRowSelection, tableColumns]);
📜 Review details
Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (22)
app/client/src/widgets/TableWidgetV2/component/Constants.ts
(1 hunks)app/client/src/widgets/TableWidgetV2/component/StaticTable.tsx
(0 hunks)app/client/src/widgets/TableWidgetV2/component/StaticTable/StaticTableBody/index.tsx
(1 hunks)app/client/src/widgets/TableWidgetV2/component/StaticTable/index.tsx
(1 hunks)app/client/src/widgets/TableWidgetV2/component/Table.tsx
(7 hunks)app/client/src/widgets/TableWidgetV2/component/TableBody/index.tsx
(0 hunks)app/client/src/widgets/TableWidgetV2/component/TableBodyCoreComponents/Row.tsx
(3 hunks)app/client/src/widgets/TableWidgetV2/component/TableBodyCoreComponents/VirtualList.tsx
(2 hunks)app/client/src/widgets/TableWidgetV2/component/TableContext.tsx
(1 hunks)app/client/src/widgets/TableWidgetV2/component/VirtualTable.tsx
(0 hunks)app/client/src/widgets/TableWidgetV2/component/VirtualTable/InifiniteScrollBody/index.tsx
(2 hunks)app/client/src/widgets/TableWidgetV2/component/VirtualTable/VirtualTableBodyComponent.tsx
(1 hunks)app/client/src/widgets/TableWidgetV2/component/VirtualTable/VirtualTableInnerElement.tsx
(1 hunks)app/client/src/widgets/TableWidgetV2/component/VirtualTable/index.tsx
(1 hunks)app/client/src/widgets/TableWidgetV2/component/cellComponents/EmptyCell.tsx
(2 hunks)app/client/src/widgets/TableWidgetV2/component/cellComponents/HeaderCell.tsx
(9 hunks)app/client/src/widgets/TableWidgetV2/component/header/BannerNActions.tsx
(1 hunks)app/client/src/widgets/TableWidgetV2/component/header/TableColumnHeader.tsx
(3 hunks)app/client/src/widgets/TableWidgetV2/component/header/VirtualTableInnerElement.tsx
(0 hunks)app/client/src/widgets/TableWidgetV2/component/header/index.tsx
(1 hunks)app/client/src/widgets/TableWidgetV2/component/index.tsx
(2 hunks)app/client/src/widgets/TableWidgetV2/component/types.ts
(1 hunks)
💤 Files with no reviewable changes (4)
- app/client/src/widgets/TableWidgetV2/component/VirtualTable.tsx
- app/client/src/widgets/TableWidgetV2/component/StaticTable.tsx
- app/client/src/widgets/TableWidgetV2/component/TableBody/index.tsx
- app/client/src/widgets/TableWidgetV2/component/header/VirtualTableInnerElement.tsx
✅ Files skipped from review due to trivial changes (1)
- app/client/src/widgets/TableWidgetV2/component/Constants.ts
🧰 Additional context used
🪛 Biome (1.9.4)
app/client/src/widgets/TableWidgetV2/component/VirtualTable/index.tsx
[error] 8-9: An empty interface is equivalent to {}.
Safe fix: Use a type alias instead.
(lint/suspicious/noEmptyInterface)
app/client/src/widgets/TableWidgetV2/component/StaticTable/index.tsx
[error] 7-8: An empty interface is equivalent to {}.
Safe fix: Use a type alias instead.
(lint/suspicious/noEmptyInterface)
⏰ Context from checks skipped due to timeout of 90000ms (3)
- GitHub Check: perform-test / rts-build / build
- GitHub Check: perform-test / client-build / client-build
- GitHub Check: perform-test / server-build / server-unit-tests
🔇 Additional comments (30)
app/client/src/widgets/TableWidgetV2/component/header/index.tsx (1)
51-56
: Validate Memo DependenciesUsing
React.useMemo
here is optimal. However, ensure that all sources influencingcolumns
are properly included in its dependency array. Ifcolumns
is derived from dynamic data, confirm that the memo remains valid on each relevant update.app/client/src/widgets/TableWidgetV2/component/header/BannerNActions.tsx (1)
1-5
: Verify Import PathsThe new
BannerNActions
component correctly imports fromactions
andbanner
. Ensure that any rename or path relocations get updated consistently across the codebase.app/client/src/widgets/TableWidgetV2/component/cellComponents/HeaderCell.tsx (11)
159-169
: Great use of the central hook for table props.
This centralizes state effectively and simplifies prop management.
172-172
: No issue with spreading header props.
This is a standard approach and looks good.
182-182
: ConfirmisResizingColumn
type.
EnsureisResizingColumn
is a ref to avoid runtime errors calling.current
.
193-193
: Validate negative index usage for sorting.
Check if thesortTableColumn(-1, ...)
call gracefully handles "no sort" scenarios.
199-201
: Readability of editable checks.
This chain of conditions is clear and concise.
274-276
: Horizontal alignment logic is clear.
No issues in referencingcolumnProperties.horizontalAlignment
.
286-286
: Menu visibility condition.
The condition for hiding the menu is straightforward and looks correct.
296-306
: Sorting actions on menu items.
Both ascending and descending sort calls appear consistent.
317-321
: Freeze column menu items.
Conditional disabling and check icons are used effectively.Also applies to: 329-333
347-347
: Portal class referencing the widget ID.
This namespacing strategy is good for preventing clashes.
365-366
: Resizer props integration.
The resizing logic is properly delegated togetResizerProps()
.app/client/src/widgets/TableWidgetV2/component/Table.tsx (9)
1-3
: Imports look good.
No issues noticed. The new references to Blueprint libraries, React Table, and related modules are appropriately declared.Also applies to: 7-7, 9-15, 17-17, 21-21, 23-24, 27-27, 29-30
120-132
: Check for zero or negative page sizes.
Consider verifying ifprops.pageSize
can be zero or negative, which could lead to unexpected logic or infinitepageCount
scenarios.
214-224
: Confirm partial row selection toggle logic.
toggleAllRowSelect(!Boolean(rowSelectionState), page)
also clears partial selections (state = 2). Double-check that this aligns with the intended tri-state selection behavior.🧰 Tools
🪛 Biome (1.9.4)
[error] 219-219: Avoid redundant
Boolean
callIt is not necessary to use
Boolean
call when a value will already be coerced to a boolean.
Unsafe fix: Remove redundantBoolean
call(lint/complexity/noExtraBooleanCast)
224-239
: Header visibility logic is clear.
Memoizing the condition for showing the header based on search, filters, and other flags is straightforward and readable.
255-269
: Good approach to determining virtualization.
LeveragingisInfiniteScrollEnabled
or checking for HTML columns is a clear strategy for deciding between static rendering and virtual scrolling.
282-296
: Proper centralization with TableProvider.
Handing off table-specific props and state to a provider nicely simplifies the rest of the component.
326-326
: Conditional table header is well-structured.
Showing the header only when necessary avoids clutter.
338-338
: Swapping between VirtualTable and StaticTable is neat.
This ternary-based approach cleanly handles different rendering modes.
342-342
: No additional remarks.app/client/src/widgets/TableWidgetV2/component/StaticTable/StaticTableBody/index.tsx (1)
1-21
: StaticTableBodyComponent is straightforward.
Mapping overrows
to render aRow
for each entry, and renderingEmptyRows
ifpageSize
exceeds the number of rows, is efficient and clear.app/client/src/widgets/TableWidgetV2/component/StaticTable/index.tsx (1)
1-7
: StaticTable implementation looks good.
Forwarding the ref toSimpleBar
and composing column headers with the table body is well-structured and easy to follow.Also applies to: 9-21
app/client/src/widgets/TableWidgetV2/component/VirtualTable/VirtualTableBodyComponent.tsx (1)
1-30
: LGTM! Clean implementation with proper separation of concerns.The component follows React best practices by using forwardRef and centralizing state management through the useAppsmithTable hook.
app/client/src/widgets/TableWidgetV2/component/TableBodyCoreComponents/VirtualList.tsx (1)
37-37
:❓ Verification inconclusive
Breaking Change:
outerRef
is now required.Making
outerRef
required could break existing implementations that don't provide this prop. Ensure all consumers of this component are updated.
🏁 Script executed:
#!/bin/bash # Find all usages of BaseVirtualListProps or VirtualList components ast-grep --pattern 'type $_ = BaseVirtualListProps' ast-grep --pattern 'const $_ = <(FixedVirtualList|FixedInfiniteVirtualList)'Length of output: 76644
Action Required: Verify Consumers for
outerRef
UpdateThe change making
outerRef: Ref<SimpleBar>
required in VirtualList (line 37) is a breaking change that could impact any consumer not supplying this prop. Our shell searches did not reveal any clear usages of BaseVirtualListProps or the FixedVirtualList/FixedInfiniteVirtualList components, so we couldn’t confirm whether current consumers cover this new requirement. Please ensure that:
- Every instance of VirtualList / its wrapped components is updated to pass a valid
outerRef
.- Manual verification is done for all consumers (particularly within TableWidgetV2) to prevent runtime errors.
app/client/src/widgets/TableWidgetV2/component/header/TableColumnHeader.tsx (2)
16-29
: Well-structured state management using context!The refactoring successfully centralizes state management by using the
useAppsmithTable
hook, which reduces prop drilling and improves code organization.
30-36
:❓ Verification inconclusive
Verify handling of zero totalColumnsWidth
While the memoization is correct, verify that the component handles cases where
totalColumnsWidth
is 0 appropriately.
🏁 Script executed:
#!/bin/bash # Search for tests or handling of zero totalColumnsWidth rg -A 5 "totalColumnsWidth.*[=><].*0"Length of output: 39
Verify zero
totalColumnsWidth
edge case handlingThe memoization implementation is correct; however, no explicit tests or handling for when
totalColumnsWidth
is 0 were found in the repository. Please verify manually that whentotalColumnsWidth
is 0, the component computesheaderWidth
as expected (i.e., it should remain 0 rather than unexpectedly applying the multi-selection width). If this is an important edge case, consider adding a dedicated unit test to cover this behavior.app/client/src/widgets/TableWidgetV2/component/cellComponents/EmptyCell.tsx (1)
23-32
: Clean function signature refactoring!The function has been successfully simplified by leveraging the context hook while maintaining its core functionality.
app/client/src/widgets/TableWidgetV2/component/index.tsx (1)
238-238
: Good default value handling!Adding a default value for
compactMode
ensures consistent behavior when the prop is not provided.
app/client/src/widgets/TableWidgetV2/component/VirtualTable/VirtualTableInnerElement.tsx
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (4)
app/client/src/widgets/TableWidgetV2/component/StaticTable/__tests__/StaticTable.test.tsx (2)
8-21
: Consider adding error cases to SimpleBar mock.The SimpleBar mock is well-structured but only handles the happy path. Consider adding test cases for error scenarios.
jest.mock("simplebar-react", () => { return { __esModule: true, default: React.forwardRef< HTMLDivElement, { children: React.ReactNode; style?: React.CSSProperties } >((props, ref) => ( + // Add error handling for invalid props + !props ? null : <div className="simplebar-content-wrapper" ref={ref} style={props.style}> {props.children} </div> )), }; });
55-62
: Add test for table rendering with empty data.The test suite covers basic rendering but misses edge cases. Consider adding a test for rendering with empty data.
it("renders correctly with empty data", () => { (useAppsmithTable as jest.Mock).mockReturnValue({ scrollContainerStyles: { height: 0, width: 0 }, }); const ref = React.createRef<SimpleBar>(); render(<StaticTable ref={ref} />); expect(screen.getByTestId("mock-table-header")).toBeInTheDocument(); expect(screen.getByTestId("mock-table-body")).toBeInTheDocument(); });app/client/src/widgets/TableWidgetV2/component/TableContext.test.tsx (1)
287-308
: Enhance memoization test with multiple re-renders.The current memoization test only checks a single re-render. Consider testing multiple re-renders and prop changes.
it("maintains memoization across multiple re-renders", () => { const { rerender } = render( <TableProvider {...mockTableProviderProps}> <TestParent /> </TableProvider> ); const firstText = screen.getByText(/.+/).textContent; // Multiple re-renders with same props for (let i = 0; i < 5; i++) { rerender( <TableProvider {...mockTableProviderProps}> <TestParent /> </TableProvider> ); expect(screen.getByText(/.+/).textContent).toBe(firstText); } // Re-render with different props rerender( <TableProvider {...{ ...mockTableProviderProps, width: 900 }}> <TestParent /> </TableProvider> ); expect(screen.getByText(/.+/).textContent).not.toBe(firstText); });app/client/src/widgets/TableWidgetV2/component/StaticTable/__tests__/StaticTableBody.test.tsx (1)
122-136
: Enhance performance test with timing measurements.The performance test verifies rendering of large datasets but doesn't measure actual performance metrics.
it("[Performance] should render large datasets within performance budget", () => { const largeDataSet = Array.from({ length: 1000 }, (_, index) => createMockRow(index, index) ); (useAppsmithTable as jest.Mock).mockReturnValue({ ...mockTableContextState, pageSize: 1000, subPage: largeDataSet, } as unknown as TableContextState); const start = performance.now(); const { getAllByTestId } = render(<StaticTableBodyComponent />); const end = performance.now(); expect(getAllByTestId("mocked-row")).toHaveLength(1000); expect(end - start).toBeLessThan(100); // 100ms budget });
📜 Review details
Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
app/client/src/widgets/TableWidgetV2/component/StaticTable/__tests__/StaticTable.test.tsx
(1 hunks)app/client/src/widgets/TableWidgetV2/component/StaticTable/__tests__/StaticTableBody.test.tsx
(1 hunks)app/client/src/widgets/TableWidgetV2/component/TableContext.test.tsx
(1 hunks)
🔇 Additional comments (1)
app/client/src/widgets/TableWidgetV2/component/StaticTable/__tests__/StaticTableBody.test.tsx (1)
30-37
: LGTM! Well-structured test helper function.The
createMockRow
helper function is well-implemented and reusable across tests.
Description
Refactor TableWidget for Improved Organization, Context Management & Rendering Efficiency
Overview
This PR restructures the TableWidget to enhance code organization, improve context management, and optimize rendering performance. Key improvements include extracting reusable components, introducing a shared TableContext, and simplifying table rendering logic.
Key Changes
✅ Improved Table Header Organization & Context Management
✅ Refactored Table & Virtualization Logic
✅ Simplified Header Components
✅ Enhanced Empty Row Handling
✅ Reorganized Core Components(Static table, virtual table)
Why These Changes?
Fixes #39308
or
Fixes
Issue URL
Warning
If no issue exists, please create an issue first, and check with the maintainers if the issue is valid.
Automation
/ok-to-test tags="@tag.Table"
🔍 Cypress test results
Tip
🟢 🟢 🟢 All cypress tests have passed! 🎉 🎉 🎉
Workflow run: https://github.com/appsmithorg/appsmith/actions/runs/13454159405
Commit: 6e90439
Cypress dashboard.
Tags:
@tag.Table
Spec:
Fri, 21 Feb 2025 10:25:20 UTC
Communication
Should the DevRel and Marketing teams inform users about this change?
Summary by CodeRabbit
New Features
BannerNActions
component for improved in-table controls and actions.Bug Fixes
Refactor