Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 3 additions & 81 deletions src/React/render.ts
Original file line number Diff line number Diff line change
@@ -1,106 +1,28 @@
import type * as React from 'react';
import * as ReactDOM from 'react-dom';
import { createRoot } from 'react-dom/client';
import type { Root } from 'react-dom/client';

// Let compiler not to search module usage
const fullClone = {
...ReactDOM,
} as typeof ReactDOM & {
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED?: {
usingClientEntryPoint?: boolean;
};
createRoot?: CreateRoot;
};

type CreateRoot = (container: ContainerType) => Root;

const { version, render: reactRender, unmountComponentAtNode } = fullClone;

let createRoot: CreateRoot;
try {
const mainVersion = Number((version || '').split('.')[0]);
if (mainVersion >= 18) {
({ createRoot } = fullClone);
}
} catch (e) {
// Do nothing;
}

function toggleWarning(skip: boolean) {
const { __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED } = fullClone;

if (
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED &&
typeof __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED === 'object'
) {
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.usingClientEntryPoint =
skip;
}
}

const MARK = '__rc_react_root__';

// ========================== Render ==========================
type ContainerType = (Element | DocumentFragment) & {
[MARK]?: Root;
};

function modernRender(node: React.ReactElement, container: ContainerType) {
toggleWarning(true);
export function render(node: React.ReactElement, container: ContainerType) {
const root = container[MARK] || createRoot(container);
toggleWarning(false);

root.render(node);

container[MARK] = root;
}

function legacyRender(node: React.ReactElement, container: ContainerType) {
reactRender?.(node, container);
}

/** @private Test usage. Not work in prod */
export function _r(node: React.ReactElement, container: ContainerType) {
if (process.env.NODE_ENV !== 'production') {
return legacyRender(node, container);
}
}

export function render(node: React.ReactElement, container: ContainerType) {
if (createRoot) {
modernRender(node, container);
return;
}

legacyRender(node, container);
}

// ========================= Unmount ==========================
async function modernUnmount(container: ContainerType) {
export async function unmount(container: ContainerType) {
// Delay to unmount to avoid React 18 sync warning
return Promise.resolve().then(() => {
container[MARK]?.unmount();

delete container[MARK];
});
}

function legacyUnmount(container: ContainerType) {
unmountComponentAtNode(container);
}

/** @private Test usage. Not work in prod */
export function _u(container: ContainerType) {
if (process.env.NODE_ENV !== 'production') {
return legacyUnmount(container);
}
}

export async function unmount(container: ContainerType) {
if (createRoot !== undefined) {
// Delay to unmount to avoid React 18 sync warning
return modernUnmount(container);
}

legacyUnmount(container);
}
21 changes: 1 addition & 20 deletions tests/react.test.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import { act } from '@testing-library/react';
import { _r, _u, render, unmount } from '../src/React/render';

globalThis.IS_REACT_ACT_ENVIRONMENT = true;
import { render, unmount } from '../src/React/render';

describe('React', () => {
afterEach(() => {
Expand Down Expand Up @@ -30,21 +28,4 @@ describe('React', () => {

expect(errorSpy).not.toHaveBeenCalled();
});

it('React 17 render & unmount', async () => {
const div = document.createElement('div');
document.body.appendChild(div);

// Mount
act(() => {
_r(<div className="bamboo" />, div);
});
expect(div.querySelector('.bamboo')).toBeTruthy();

// Unmount
act(() => {
_u(div);
});
expect(div.querySelector('.bamboo')).toBeFalsy();
});
});
Loading