diff --git a/enterprise/frontend/src/embedding-sdk/lib/polyfill-use-sync-external-store.ts b/enterprise/frontend/src/embedding-sdk/lib/polyfill-use-sync-external-store.ts index 8a6996db8f743eb0648b34c7fc9b8ccbcb19442c..c4053599a9d8cec2c85d620ab3cb9f953b6f093c 100644 --- a/enterprise/frontend/src/embedding-sdk/lib/polyfill-use-sync-external-store.ts +++ b/enterprise/frontend/src/embedding-sdk/lib/polyfill-use-sync-external-store.ts @@ -1,11 +1,11 @@ import React from "react"; import { useSyncExternalStore } from "use-sync-external-store/shim"; -import { isReact17OrEarlier } from "metabase/lib/react-compat"; - // Monkey-patches useSyncExternalStore if we are in React 17, // where useSyncExternalStore is not available. // This is used in react-redux v8, until they've dropped the shim in v9. -if (isReact17OrEarlier()) { +export const shouldShimExternalStore = () => !("useSyncExternalStore" in React); + +if (shouldShimExternalStore()) { React.useSyncExternalStore = useSyncExternalStore; } diff --git a/frontend/src/metabase/lib/compat/check-version.ts b/frontend/src/metabase/lib/compat/check-version.ts new file mode 100644 index 0000000000000000000000000000000000000000..7196128312f36fc069a5345da2cbd338836cb845 --- /dev/null +++ b/frontend/src/metabase/lib/compat/check-version.ts @@ -0,0 +1,7 @@ +import React from "react"; + +export const getMajorReactVersion = () => { + const versionParts = React.version.split(".").map(Number); + + return versionParts[0]; +}; diff --git a/frontend/src/metabase/lib/compat/check-version.unit.spec.ts b/frontend/src/metabase/lib/compat/check-version.unit.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..1197d4ceddf798352d1a38eb91c7b1dedff42aed --- /dev/null +++ b/frontend/src/metabase/lib/compat/check-version.unit.spec.ts @@ -0,0 +1,41 @@ +import React from "react"; + +import { getMajorReactVersion } from "./check-version"; + +describe("getMajorReactVersion", () => { + const versions = [ + { version: "0.14.0", expected: 0 }, + { version: "15.0.0", expected: 15 }, + { version: "16.0.0", expected: 16 }, + { version: "16.8.0", expected: 16 }, + { version: "17.0.0", expected: 17 }, + { version: "17.0.2", expected: 17 }, + { version: "18.0.0", expected: 18 }, + { version: "18.1.0", expected: 18 }, + ]; + + beforeEach(() => { + jest.resetModules(); + }); + + it.each(versions)( + "should return $expected for React version $version", + ({ version, expected }) => { + Object.defineProperty(React, "version", { + value: version, + writable: true, + }); + + expect(getMajorReactVersion()).toBe(expected); + }, + ); + + it("should return NaN for an invalid version string", () => { + Object.defineProperty(React, "version", { + value: "invalid-version", + writable: true, + }); + + expect(getMajorReactVersion()).toBeNaN(); + }); +}); diff --git a/frontend/src/metabase/lib/react-compat.ts b/frontend/src/metabase/lib/react-compat.ts index d51c748b1ef0e381146787cc05a7b861fb222368..01a43a1c58eeade8d2e76be13c9225ae14a2000b 100644 --- a/frontend/src/metabase/lib/react-compat.ts +++ b/frontend/src/metabase/lib/react-compat.ts @@ -1,17 +1,17 @@ // Support React 17 backwards compatibility for the Embedding SDK - -import React from "react"; +import type React from "react"; import ReactDOM from "react-dom"; import { type Root, createRoot } from "react-dom/client"; -// React 18 and later has the useSyncExternalStore hook. -export const isReact17OrEarlier = () => !("useSyncExternalStore" in React); +import { getMajorReactVersion } from "./compat/check-version"; export function renderRoot( content: React.JSX.Element, element: Element, ): Root | undefined { - if (isReact17OrEarlier()) { + const reactVersion = getMajorReactVersion(); + + if (reactVersion <= 17) { ReactDOM.render(content, element); return; } @@ -23,7 +23,9 @@ export function renderRoot( } export function unmountRoot(root?: Root, element?: Element) { - if (isReact17OrEarlier() && element) { + const reactVersion = getMajorReactVersion(); + + if (reactVersion <= 17 && element) { ReactDOM.unmountComponentAtNode(element); return; } diff --git a/frontend/src/metabase/visualizations/components/TableInteractive/TableInteractive.jsx b/frontend/src/metabase/visualizations/components/TableInteractive/TableInteractive.jsx index b090fb53d9fd93f41d0d02b3ac8b305157a84c0d..83f6e4e6a01cb9f6363388772d0dfd165ce0677f 100644 --- a/frontend/src/metabase/visualizations/components/TableInteractive/TableInteractive.jsx +++ b/frontend/src/metabase/visualizations/components/TableInteractive/TableInteractive.jsx @@ -2,7 +2,6 @@ import cx from "classnames"; import PropTypes from "prop-types"; import { Component, createRef, forwardRef } from "react"; -import { findDOMNode } from "react-dom"; import { connect } from "react-redux"; import { Grid, ScrollSync } from "react-virtualized"; import { t } from "ttag"; @@ -115,6 +114,8 @@ class TableInteractive extends Component { this.headerRefs = []; this.detailShortcutRef = createRef(); + this.gridRef = createRef(); + window.METABASE_TABLE = this; } @@ -1042,7 +1043,7 @@ class TableInteractive extends Component { return; } - const scrollOffset = findDOMNode(this.grid)?.scrollTop || 0; + const scrollOffset = this.gridRef.current?.scrollTop || 0; // infer row index from mouse position when we hover the gutter column if (event?.currentTarget?.id === "gutter-column") { @@ -1309,7 +1310,7 @@ class TableInteractive extends Component { } _benchmark() { - const grid = findDOMNode(this.grid); + const grid = this.gridRef.current; const height = grid.scrollHeight; let top = 0; let start = Date.now();