Skip to content

Commit 2376cae

Browse files
committed
Fix: properly handling global react-dom@18
1 parent 4146244 commit 2376cae

File tree

4 files changed

+53
-53
lines changed

4 files changed

+53
-53
lines changed

src/ReactHTMLElement.ts

Lines changed: 8 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,16 @@
1-
import { version as reactVersion } from 'react';
2-
import ReactDOM from 'react-dom';
3-
import type { Root, createRoot as createRootOriginal } from 'react-dom/client';
1+
import type ReactDOM from 'react-dom';
2+
import type { Root } from 'react-dom/client';
3+
import { createRoot } from './react-dom-client';
44

55
type Renderable = Parameters<ReactDOM.Renderer>[0][number];
6-
7-
const [, major] = /^(\d+)\.\d+\.\d+$/.exec(reactVersion) || [undefined, '16'];
8-
const reactMajor = Number(major);
9-
10-
const isPreEighteen = reactMajor < 18;
11-
const REACT_DOM_CLIENT_IMPORT = isPreEighteen
12-
? './react-dom-client-polyfill'
13-
: 'react-dom/client';
6+
type ReactHTMLElementDOMRoot = Pick<Root, 'render' | 'unmount'>;
147

158
class ReactHTMLElement extends HTMLElement {
169
private _initialized?: boolean;
1710

1811
private _mountPoint?: Element;
1912

20-
private _root?: Root;
13+
private _root?: ReactHTMLElementDOMRoot;
2114

2215
private getShadowRoot(): ShadowRoot {
2316
return this.shadowRoot || this.attachShadow({ mode: 'open' });
@@ -49,43 +42,27 @@ class ReactHTMLElement extends HTMLElement {
4942
this._mountPoint = mount;
5043
}
5144

52-
async root(): Promise<Root> {
45+
root(): ReactHTMLElementDOMRoot {
5346
if (this._root) return this._root;
5447

55-
const { createRoot } = (await import(
56-
/* webpackExports: ['createRoot'] */
57-
`${REACT_DOM_CLIENT_IMPORT}`
58-
)) as {
59-
createRoot: typeof createRootOriginal;
60-
};
6148
this._root = createRoot(this.mountPoint);
6249
return this._root;
6350
}
6451

6552
render(app: Renderable): void {
6653
if (!this.isConnected) return;
6754

68-
if (isPreEighteen) {
69-
ReactDOM.render(app, this.mountPoint);
70-
return;
71-
}
72-
7355
void this.renderRoot(app);
7456
}
7557

76-
async renderRoot(app: Renderable): Promise<void> {
77-
const root = await this.root();
58+
renderRoot(app: Renderable): void {
59+
const root = this.root();
7860
root.render(app);
7961
}
8062

8163
disconnectedCallback(): void {
8264
if (!this._mountPoint) return;
8365

84-
if (isPreEighteen) {
85-
ReactDOM.unmountComponentAtNode(this._mountPoint);
86-
return;
87-
}
88-
8966
this._root?.unmount();
9067
}
9168

src/__tests__/ReactHtmlElement.tests.tsx

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
11
import React, { useState, useEffect } from 'react';
2-
import { findByText, waitFor, queryByTestId } from '@testing-library/dom';
2+
import {
3+
findByText,
4+
waitFor,
5+
queryByTestId,
6+
findByTestId,
7+
} from '@testing-library/dom';
38
import '@testing-library/jest-dom/extend-expect';
49
import ReactHTMLElement from '../ReactHTMLElement';
510

611
function ReactTest({ onUnmount = (): void => undefined }): React.ReactElement {
712
const [increment, setIncrement] = useState(0);
8-
useEffect(() => (): void => onUnmount());
13+
useEffect(() => (): void => onUnmount(), []);
914
return (
1015
<div data-testid="container">
1116
<button
@@ -39,9 +44,10 @@ async function getDocument(
3944
testElement.onUnmount = onUnmount;
4045
document.body.appendChild(testElement);
4146
await waitFor(() => expect(testElement.shadowRoot).toBeTruthy());
42-
return testElement.shadowRoot?.querySelector(
43-
'div[data-testid=container]'
44-
) as HTMLElement;
47+
return findByTestId(
48+
(testElement.shadowRoot as unknown) as HTMLElement,
49+
'container'
50+
);
4551
}
4652

4753
it('renders interactable react', async () => {

src/react-dom-client-polyfill.ts

Lines changed: 0 additions & 17 deletions
This file was deleted.

src/react-dom-client.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import * as ReactDOM from 'react-dom';
2+
import type { createRoot as createRootOriginal } from 'react-dom/client';
3+
4+
type ReactDOM18 = ReactDOMOriginal & {
5+
createRoot?: CreateRoot;
6+
};
7+
8+
let MaybeReactDOM18: ReactDOM18;
9+
try {
10+
// eslint-disable-next-line global-require,@typescript-eslint/no-var-requires
11+
MaybeReactDOM18 = require('react-dom/client') as ReactDOM18;
12+
} catch {
13+
MaybeReactDOM18 = ReactDOM as ReactDOM18;
14+
}
15+
16+
type CreateRoot = typeof createRootOriginal;
17+
type CreateRootParams = Parameters<CreateRoot>;
18+
type ReactDOMOriginal = typeof ReactDOM;
19+
type RendererProps = Parameters<typeof ReactDOM['render']>;
20+
21+
const createRootFake = (container: CreateRootParams[0]) => {
22+
const newRoot = {
23+
render: (element: RendererProps[0]) => {
24+
ReactDOM.render(element, container);
25+
},
26+
unmount: () => {
27+
ReactDOM.unmountComponentAtNode(container);
28+
},
29+
};
30+
return newRoot;
31+
};
32+
33+
// eslint-disable-next-line import/prefer-default-export
34+
export const { createRoot = createRootFake } = MaybeReactDOM18;

0 commit comments

Comments
 (0)