UNPKG

openstack-uicore-foundation

Version:

ui reactjs components for openstack marketing site

84 lines (58 loc) 6.04 kB
# showConfirmDialog React 16 Compatibility Fix Plan Created: 2026-04-09 Author: smarcet@gmail.com Status: VERIFIED Approved: Yes Iterations: 0 Worktree: No Type: Bugfix ## Summary **Symptom:** `showConfirmDialog` causes "Module not found: Error: Can't resolve 'react-dom/client'" build error on React 16 projects, despite a `/* webpackIgnore: true */` dynamic import guard. **Trigger:** Any consuming project on React 16 that bundles `openstack-uicore-foundation` hits the error during webpack build. **Root Cause:** `src/components/mui/showConfirmDialog.js:27` — The `import(/* webpackIgnore: true */ "react-dom/client")` compiles into the UMD output as a bare `import("react-dom/client")` (the magic comment is stripped during this library's build). When a consuming project's webpack processes the compiled bundle, it encounters the bare dynamic import, tries to resolve `react-dom/client`, and fails because React 16 has no such module path. ## Investigation - **Evolution:** Original code (cd39cf3) used only `ReactDOM.render()`. PR #214 (094c6b9) added `require("react-dom/client")` in try/catch for React 18+ support — caused the same build error. Commit 6c95b33 replaced `require()` with `import(/* webpackIgnore: true */ ...)` — the magic comment works for THIS library's webpack but is stripped from the compiled UMD output. - **Confirmed in built output:** `lib/components/mui/show-confirm-dialog.js` contains literal `await import("react-dom/client")` with no webpack annotation — any consuming webpack resolves it at build time. - **Key insight:** The `webpackIgnore` comment is a build-time directive that only affects the webpack instance that processes the source file. It does not survive into the output and cannot protect consuming projects. - **Internal callers** (5 files: mui-table, mui-table-sortable, mui-table-editable, meta-field-values, additional-input-list) all use `showConfirmDialog` as a plain imperative function — `const isConfirmed = await showConfirmDialog({...})`. ## Fix Approach **Chosen:** Bridge Pattern + Context Provider **Why:** Completely eliminates all references to `react-dom/client` from the source and compiled output. The dialog renders inside the existing React tree (managed by whatever React version the app uses), so no version-specific rendering API is needed. Preserves the imperative `showConfirmDialog()` API unchanged. **Alternatives considered:** - *Configuration injection* (`configureConfirmDialog({ createRoot })`) — simpler but less ergonomic; consumer must handle version detection and import themselves. - *Remove createRoot entirely* (pure `ReactDOM.render`) — works on 16/17/18 but breaks React 19 where `ReactDOM.render` was removed. **How it works:** 1. A new `ConfirmDialogProvider` component manages dialog state (open/closed, options, resolve callback) and renders `ConfirmDialog` within the existing React tree. 2. On mount, the provider registers a "bridge" callback in a module-level variable inside `showConfirmDialog.js`. 3. When `showConfirmDialog()` is called, it delegates to the bridge if registered. If no provider is mounted, it falls back to `ReactDOM.render()` with a console warning (backward compat for React 16/17/18 consumers who haven't added the provider yet). 4. Zero references to `react-dom/client` remain in the codebase. **Files:** - `src/components/mui/showConfirmDialog.js` — Add bridge registration, fallback logic - `src/components/mui/ConfirmDialogProvider.js` — NEW: Provider component - `src/components/index.js` — Export the new provider - `webpack.common.js` — Add entry point for the new provider **Tests:** - `src/components/mui/__tests__/show-confirm-dialog.test.js` — Update for bridge-based flow - `src/components/mui/__tests__/confirm-dialog-provider.test.js` — NEW: Provider tests **Defense-in-depth:** Not applicable — this is a build-time resolution error, not a data flow issue. ## Progress - [x] Task 1: Implement bridge pattern + provider - [x] Task 2: Verify **Tasks:** 2 | **Done:** 2 ## Tasks ### Task 1: Implement bridge pattern + provider **Objective:** Replace the `react-dom/client` dynamic import with a bridge pattern. Create `ConfirmDialogProvider`, modify `showConfirmDialog` to use bridge with ReactDOM.render fallback, export the new provider. **Files:** - `src/components/mui/showConfirmDialog.js` — Remove `getCreateRoot()`, add bridge registration exports (`_registerBridge`, `_unregisterBridge`). When bridge is registered, delegate. When not, fall back to `ReactDOM.render()` with `console.warn` suggesting provider migration. - `src/components/mui/ConfirmDialogProvider.js` — NEW: Functional component using `useState` + `useEffect`. On mount, registers bridge via `_registerBridge`. Bridge callback sets dialog state and returns a Promise. Renders `<ConfirmDialog>` when dialog state is active. On unmount, calls `_unregisterBridge`. - `src/components/index.js` — Add export: `export { default as ConfirmDialogProvider } from './mui/ConfirmDialogProvider'` - `webpack.common.js` — Add entry: `'components/mui/confirm-dialog-provider': './src/components/mui/ConfirmDialogProvider.js'` **TDD:** 1. Write regression test: import `showConfirmDialog` without mocking `react-dom/client` — verify no reference to `react-dom/client` exists in the module 2. Write provider test: render `ConfirmDialogProvider`, call `showConfirmDialog`, verify dialog appears and resolves correctly 3. Write fallback test: call `showConfirmDialog` without provider mounted — verify `ReactDOM.render` is called with console warning 4. Verify all existing tests still pass (callers mock `showConfirmDialog` so they are unaffected) **Verify:** `npx jest --no-cache` ### Task 2: Verify **Objective:** Full test suite passes, no regressions across all components that use `showConfirmDialog`. **Verify:** `npx jest --no-cache && grep -r "react-dom/client" src/ && echo "FAIL: react-dom/client still referenced" || echo "PASS: no react-dom/client references"`