Skip to content

Commit

Permalink
Fix: properly handling global react-dom@18
Browse files Browse the repository at this point in the history
  • Loading branch information
stevematney committed Jan 10, 2023
1 parent 4146244 commit 2376cae
Show file tree
Hide file tree
Showing 4 changed files with 53 additions and 53 deletions.
39 changes: 8 additions & 31 deletions src/ReactHTMLElement.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,16 @@
import { version as reactVersion } from 'react';
import ReactDOM from 'react-dom';
import type { Root, createRoot as createRootOriginal } from 'react-dom/client';
import type ReactDOM from 'react-dom';
import type { Root } from 'react-dom/client';
import { createRoot } from './react-dom-client';

type Renderable = Parameters<ReactDOM.Renderer>[0][number];

const [, major] = /^(\d+)\.\d+\.\d+$/.exec(reactVersion) || [undefined, '16'];
const reactMajor = Number(major);

const isPreEighteen = reactMajor < 18;
const REACT_DOM_CLIENT_IMPORT = isPreEighteen
? './react-dom-client-polyfill'
: 'react-dom/client';
type ReactHTMLElementDOMRoot = Pick<Root, 'render' | 'unmount'>;

class ReactHTMLElement extends HTMLElement {
private _initialized?: boolean;

private _mountPoint?: Element;

private _root?: Root;
private _root?: ReactHTMLElementDOMRoot;

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

async root(): Promise<Root> {
root(): ReactHTMLElementDOMRoot {
if (this._root) return this._root;

const { createRoot } = (await import(
/* webpackExports: ['createRoot'] */
`${REACT_DOM_CLIENT_IMPORT}`
)) as {
createRoot: typeof createRootOriginal;
};
this._root = createRoot(this.mountPoint);
return this._root;
}

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

if (isPreEighteen) {
ReactDOM.render(app, this.mountPoint);
return;
}

void this.renderRoot(app);
}

async renderRoot(app: Renderable): Promise<void> {
const root = await this.root();
renderRoot(app: Renderable): void {
const root = this.root();
root.render(app);
}

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

if (isPreEighteen) {
ReactDOM.unmountComponentAtNode(this._mountPoint);
return;
}

this._root?.unmount();
}

Expand Down
16 changes: 11 additions & 5 deletions src/__tests__/ReactHtmlElement.tests.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
import React, { useState, useEffect } from 'react';
import { findByText, waitFor, queryByTestId } from '@testing-library/dom';
import {
findByText,
waitFor,
queryByTestId,
findByTestId,
} from '@testing-library/dom';
import '@testing-library/jest-dom/extend-expect';
import ReactHTMLElement from '../ReactHTMLElement';

function ReactTest({ onUnmount = (): void => undefined }): React.ReactElement {
const [increment, setIncrement] = useState(0);
useEffect(() => (): void => onUnmount());
useEffect(() => (): void => onUnmount(), []);
return (
<div data-testid="container">
<button
Expand Down Expand Up @@ -39,9 +44,10 @@ async function getDocument(
testElement.onUnmount = onUnmount;
document.body.appendChild(testElement);
await waitFor(() => expect(testElement.shadowRoot).toBeTruthy());
return testElement.shadowRoot?.querySelector(
'div[data-testid=container]'
) as HTMLElement;
return findByTestId(
(testElement.shadowRoot as unknown) as HTMLElement,
'container'
);
}

it('renders interactable react', async () => {
Expand Down
17 changes: 0 additions & 17 deletions src/react-dom-client-polyfill.ts

This file was deleted.

34 changes: 34 additions & 0 deletions src/react-dom-client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import * as ReactDOM from 'react-dom';
import type { createRoot as createRootOriginal } from 'react-dom/client';

type ReactDOM18 = ReactDOMOriginal & {
createRoot?: CreateRoot;
};

let MaybeReactDOM18: ReactDOM18;
try {
// eslint-disable-next-line global-require,@typescript-eslint/no-var-requires
MaybeReactDOM18 = require('react-dom/client') as ReactDOM18;
} catch {
MaybeReactDOM18 = ReactDOM as ReactDOM18;
}

type CreateRoot = typeof createRootOriginal;
type CreateRootParams = Parameters<CreateRoot>;
type ReactDOMOriginal = typeof ReactDOM;
type RendererProps = Parameters<typeof ReactDOM['render']>;

const createRootFake = (container: CreateRootParams[0]) => {
const newRoot = {
render: (element: RendererProps[0]) => {
ReactDOM.render(element, container);
},
unmount: () => {
ReactDOM.unmountComponentAtNode(container);
},
};
return newRoot;
};

// eslint-disable-next-line import/prefer-default-export
export const { createRoot = createRootFake } = MaybeReactDOM18;

0 comments on commit 2376cae

Please sign in to comment.