Skip to content

feat: setup React hybrid widget infrastructure#16766

Draft
shuoweil wants to merge 1 commit intomainfrom
shuowei-anywidget-react-hybrid
Draft

feat: setup React hybrid widget infrastructure#16766
shuoweil wants to merge 1 commit intomainfrom
shuowei-anywidget-react-hybrid

Conversation

@shuoweil
Copy link
Copy Markdown
Contributor

Set up the build pipeline and file structure to support the React Hybrid Approach for TableWidget.

  • Added esbuild via Python pip to allow bundling without requiring a Node.js environment.
  • Moved the initial React implementation to bigframes/display/src/index.js and fixed relative imports.
  • Generated the bundled asset bigframes/display/table_widget.esm.js.
  • Updated anywidget.py to load the new bundled ESM file instead of the vanilla JS file.

This prepares the codebase for the core rewrite of the widget functionality in React.

Fixes #<505414691> 🦕

@shuoweil shuoweil self-assigned this Apr 22, 2026
@shuoweil shuoweil requested review from a team as code owners April 22, 2026 18:01
@shuoweil shuoweil requested review from GarrettWu and removed request for a team April 22, 2026 18:01
Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a React-based TableWidget along with bundled React and Scheduler dependencies. Several critical issues were identified, including missing files (table_widget.esm.js and react-dom.js) that are required for the widget to function. Additionally, the implementation of event listeners in the table headers lacks a cleanup mechanism, which will lead to memory leaks and duplicate event handlers.

def _esm(self):
"""Load JavaScript code from external file."""
return resources.read_text(bigframes.display, "table_widget.js")
return resources.read_text(bigframes.display, "table_widget.esm.js")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The pull request updates the code to load table_widget.esm.js, but this file is not included in the changes. As this is the bundled asset required for the widget to function, its absence will cause a FileNotFoundError at runtime.

*/

import React, { useState, useEffect, useRef } from "../react.js";
import ReactDOM from "../react-dom.js";
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The import references ../react-dom.js, but this file is missing from the pull request. While react.js and scheduler.js were added, react-dom.js is also required for the widget to render.

Comment on lines +91 to +130
header.addEventListener("mouseover", () => {
if (sortContext.findIndex((item) => item.column === columnName) === -1) {
indicatorSpan.style.visibility = "visible";
}
});
header.addEventListener("mouseout", () => {
if (sortContext.findIndex((item) => item.column === columnName) === -1) {
indicatorSpan.style.visibility = "hidden";
}
});

header.addEventListener("click", (event) => {
const currentSortIndex = sortContext.findIndex((item) => item.column === columnName);
let newContext = [...sortContext];

if (event.shiftKey) {
if (currentSortIndex !== -1) {
if (newContext[currentSortIndex].ascending) {
newContext[currentSortIndex] = { ...newContext[currentSortIndex], ascending: false };
} else {
newContext.splice(currentSortIndex, 1);
}
} else {
newContext.push({ column: columnName, ascending: true });
}
} else {
if (currentSortIndex !== -1 && newContext.length === 1) {
if (newContext[currentSortIndex].ascending) {
newContext[currentSortIndex] = { ...newContext[currentSortIndex], ascending: false };
} else {
newContext = [];
}
} else {
newContext = [{ column: columnName, ascending: true }];
}
}

model.set("sort_context", newContext);
model.save_changes();
});
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Event listeners (mouseover, mouseout, click) are attached to table headers inside a useEffect that re-runs whenever sortContext changes. Because there is no cleanup function to remove the previous listeners, they will accumulate on the DOM elements every time the user interacts with the table (e.g., clicking a header to sort). This leads to memory leaks and multiple executions of the click handler.

Consider using event delegation on the tableContainer for the click handler and CSS classes for hover effects to avoid attaching listeners to individual elements inside the effect.

@shuoweil shuoweil marked this pull request as draft April 22, 2026 18:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant