From 01c0321b1782ee3fd7d668fdc77e8af268199d51 Mon Sep 17 00:00:00 2001
From: lijianan <574980606@qq.com>
Date: Mon, 13 Oct 2025 23:14:52 +0800
Subject: [PATCH] chore: remove unstable api for React 19 compitable (#55274)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* chore: remove unstable api for React 19 compitable
* update
* update
* update
* update antd-token-previewer
* update
* update
---------
Co-authored-by: 遇见同学 <1875694521@qq.com>
---
.dumi/theme/builtins/Previewer/Actions.tsx | 1 -
.../builtins/Previewer/stackblitzConfig.ts | 1 -
.dumi/theme/layouts/GlobalLayout.tsx | 2 -
.../__snapshots__/index.test.ts.snap | 1 -
components/__tests__/unstable.test.ts | 49 -----------------
components/_util/wave/WaveEffect.tsx | 28 ++--------
.../config-provider/UnstableContext.tsx | 29 ----------
components/index.ts | 4 --
components/message/index.tsx | 7 +--
components/modal/confirm.tsx | 23 ++++----
components/notification/index.tsx | 7 +--
docs/react/v5-for-19.en-US.md | 54 -------------------
docs/react/v5-for-19.zh-CN.md | 54 -------------------
package.json | 3 +-
tests/__snapshots__/index.test.ts.snap | 1 -
15 files changed, 19 insertions(+), 245 deletions(-)
delete mode 100644 components/__tests__/unstable.test.ts
delete mode 100644 components/config-provider/UnstableContext.tsx
delete mode 100644 docs/react/v5-for-19.en-US.md
delete mode 100644 docs/react/v5-for-19.zh-CN.md
diff --git a/.dumi/theme/builtins/Previewer/Actions.tsx b/.dumi/theme/builtins/Previewer/Actions.tsx
index ea67f72c99..61b0282174 100644
--- a/.dumi/theme/builtins/Previewer/Actions.tsx
+++ b/.dumi/theme/builtins/Previewer/Actions.tsx
@@ -211,7 +211,6 @@ createRoot(document.getElementById('container')).render();
'react-dom': '^19.0.0',
'@types/react': '^19.0.0',
'@types/react-dom': '^19.0.0',
- '@ant-design/v5-patch-for-react-19': '^1.0.3',
},
demoJsContent,
indexCssContent,
diff --git a/.dumi/theme/builtins/Previewer/stackblitzConfig.ts b/.dumi/theme/builtins/Previewer/stackblitzConfig.ts
index 3d147caf6e..f34715694b 100644
--- a/.dumi/theme/builtins/Previewer/stackblitzConfig.ts
+++ b/.dumi/theme/builtins/Previewer/stackblitzConfig.ts
@@ -122,7 +122,6 @@ const getStackblitzConfig = ({
// main.tsx
[`src/main.${_suffix}`]: `import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
-import '@ant-design/v5-patch-for-react-19';
import Demo from './demo';
createRoot(document.getElementById('container')${suffix === 'tsx' ? '!' : ''}).render(
diff --git a/.dumi/theme/layouts/GlobalLayout.tsx b/.dumi/theme/layouts/GlobalLayout.tsx
index b6f602283e..38d46b9c18 100644
--- a/.dumi/theme/layouts/GlobalLayout.tsx
+++ b/.dumi/theme/layouts/GlobalLayout.tsx
@@ -22,8 +22,6 @@ import type { SimpleComponentClassNames, SiteContextProps } from '../slots/SiteC
import SiteContext from '../slots/SiteContext';
import { isLocalStorageNameSupported } from '../utils';
-import '@ant-design/v5-patch-for-react-19';
-
type SiteState = Partial>;
const RESPONSIVE_MOBILE = 768;
diff --git a/components/__tests__/__snapshots__/index.test.ts.snap b/components/__tests__/__snapshots__/index.test.ts.snap
index 75e5d985b7..24753185e6 100644
--- a/components/__tests__/__snapshots__/index.test.ts.snap
+++ b/components/__tests__/__snapshots__/index.test.ts.snap
@@ -74,7 +74,6 @@ exports[`antd exports modules correctly 1`] = `
"message",
"notification",
"theme",
- "unstableSetRender",
"version",
]
`;
diff --git a/components/__tests__/unstable.test.ts b/components/__tests__/unstable.test.ts
deleted file mode 100644
index fa9d431bff..0000000000
--- a/components/__tests__/unstable.test.ts
+++ /dev/null
@@ -1,49 +0,0 @@
-import * as ReactDOM from 'react-dom';
-import { Modal, unstableSetRender } from 'antd';
-
-import { waitFakeTimer19 } from '../../tests/utils';
-
-// TODO: Remove this. Mock for React 19
-jest.mock('react-dom', () => {
- const realReactDOM = jest.requireActual('react-dom');
-
- if (realReactDOM.version.startsWith('19')) {
- const realReactDOMClient = jest.requireActual('react-dom/client');
- realReactDOM.createRoot = realReactDOMClient.createRoot;
- }
-
- return realReactDOM;
-});
-
-describe('unstable', () => {
- beforeEach(() => {
- jest.useFakeTimers();
- });
-
- afterEach(() => {
- jest.useRealTimers();
- });
-
- it('unstableSetRender', async () => {
- if (ReactDOM.version.startsWith('19')) {
- unstableSetRender((node, container) => {
- const root = (ReactDOM as any).createRoot(container);
- root.render(node);
- return async () => {
- root.unmount();
- };
- });
-
- Modal.info({ content: 'unstableSetRender' });
-
- await waitFakeTimer19();
-
- expect(document.querySelector('.ant-modal')).toBeTruthy();
- }
- });
-
- it('unstableSetRender without param', async () => {
- const currentRender = unstableSetRender();
- expect(currentRender).toBeInstanceOf(Function);
- });
-});
diff --git a/components/_util/wave/WaveEffect.tsx b/components/_util/wave/WaveEffect.tsx
index e7878bdb00..7ada0ff2c8 100644
--- a/components/_util/wave/WaveEffect.tsx
+++ b/components/_util/wave/WaveEffect.tsx
@@ -1,12 +1,11 @@
import * as React from 'react';
import CSSMotion from '@rc-component/motion';
import raf from '@rc-component/util/lib/raf';
+import { render, unmount } from '@rc-component/util/lib/React/render';
import { composeRef } from '@rc-component/util/lib/ref';
import { clsx } from 'clsx';
import type { WaveProps } from '.';
-import { unstableSetRender } from '../../config-provider/UnstableContext';
-import type { UnmountType } from '../../config-provider/UnstableContext';
import { TARGET_CLS } from './interface';
import type { ShowWaveEffect } from './interface';
import { getTargetWaveColor } from './util';
@@ -19,21 +18,13 @@ export interface WaveEffectProps {
className: string;
target: HTMLElement;
component?: string;
- registerUnmount: () => UnmountType | null;
colorSource?: WaveProps['colorSource'];
}
const WaveEffect = (props: WaveEffectProps) => {
- const { className, target, component, registerUnmount, colorSource } = props;
+ const { className, target, component, colorSource } = props;
const divRef = React.useRef(null);
- // ====================== Refs ======================
- const unmountRef = React.useRef(null);
-
- React.useEffect(() => {
- unmountRef.current = registerUnmount();
- }, []);
-
// ===================== Effect =====================
const [color, setWaveColor] = React.useState(null);
const [borderRadius, setBorderRadius] = React.useState([]);
@@ -129,7 +120,7 @@ const WaveEffect = (props: WaveEffectProps) => {
onAppearEnd={(_, event) => {
if (event.deadline || (event as TransitionEvent).propertyName === 'opacity') {
const holder = divRef.current?.parentElement!;
- unmountRef.current?.().then(() => {
+ unmount(holder).then(() => {
holder?.remove();
});
}
@@ -162,18 +153,7 @@ const showWaveEffect: ShowWaveEffect = (target, info) => {
holder.style.top = '0px';
target?.insertBefore(holder, target?.firstChild);
- const reactRender = unstableSetRender();
-
- let unmountCallback: UnmountType | null = null;
-
- function registerUnmount() {
- return unmountCallback;
- }
-
- unmountCallback = reactRender(
- ,
- holder,
- );
+ render(, holder);
};
export default showWaveEffect;
diff --git a/components/config-provider/UnstableContext.tsx b/components/config-provider/UnstableContext.tsx
deleted file mode 100644
index 982c58318b..0000000000
--- a/components/config-provider/UnstableContext.tsx
+++ /dev/null
@@ -1,29 +0,0 @@
-import type * as React from 'react';
-import { render, unmount } from '@rc-component/util/lib/React/render';
-
-export type UnmountType = () => Promise;
-export type RenderType = (
- node: React.ReactElement,
- container: Element | DocumentFragment,
-) => UnmountType;
-
-const defaultReactRender: RenderType = (node, container) => {
- render(node, container);
- return () => {
- return unmount(container);
- };
-};
-
-let unstableRender: RenderType = defaultReactRender;
-
-/**
- * @deprecated Set React render function for compatible usage.
- * This is internal usage only compatible with React 19.
- * And will be removed in next major version.
- */
-export function unstableSetRender(render?: RenderType) {
- if (render) {
- unstableRender = render;
- }
- return unstableRender;
-}
diff --git a/components/index.ts b/components/index.ts
index 3a642e557b..0e730c9af1 100644
--- a/components/index.ts
+++ b/components/index.ts
@@ -175,7 +175,3 @@ export type { DraggerProps, UploadFile, UploadProps } from './upload';
export { default as version } from './version';
export { default as Watermark } from './watermark';
export type { WatermarkProps } from './watermark';
-
-// TODO: Remove in v6
-/* eslint-disable-next-line perfectionist/sort-exports */
-export { unstableSetRender } from './config-provider/UnstableContext';
diff --git a/components/message/index.tsx b/components/message/index.tsx
index cc95756f4e..b24b3b1124 100755
--- a/components/message/index.tsx
+++ b/components/message/index.tsx
@@ -1,8 +1,8 @@
import React, { useContext } from 'react';
+import { render } from '@rc-component/util/lib/React/render';
import { AppConfigContext } from '../app/context';
import ConfigProvider, { ConfigContext, globalConfig, warnContext } from '../config-provider';
-import { unstableSetRender } from '../config-provider/UnstableContext';
import type {
ArgsProps,
ConfigOptions,
@@ -132,13 +132,10 @@ const flushMessageQueue = () => {
// Delay render to avoid sync issue
act(() => {
- const reactRender = unstableSetRender();
-
- reactRender(
+ render(
{
const { instance, sync } = node || {};
-
// React 18 test env will throw if call immediately in ref
Promise.resolve().then(() => {
if (!newMessage.instance && instance) {
diff --git a/components/modal/confirm.tsx b/components/modal/confirm.tsx
index ccac1d8ebd..34960cd54f 100644
--- a/components/modal/confirm.tsx
+++ b/components/modal/confirm.tsx
@@ -1,9 +1,8 @@
import React, { useContext } from 'react';
+import { render, unmount } from '@rc-component/util/lib/React/render';
import warning from '../_util/warning';
import ConfigProvider, { ConfigContext, globalConfig, warnContext } from '../config-provider';
-import { unstableSetRender } from '../config-provider/UnstableContext';
-import type { UnmountType } from '../config-provider/UnstableContext';
import type { ConfirmDialogProps } from './ConfirmDialog';
import ConfirmDialog from './ConfirmDialog';
import destroyFns from './destroyFns';
@@ -72,8 +71,6 @@ export default function confirm(config: ModalFuncProps) {
let currentConfig = { ...config, close, open: true } as any;
let timeoutId: ReturnType;
- let reactUnmount: UnmountType;
-
function destroy(...args: any[]) {
const triggerCancel = args.some((param) => param?.triggerCancel);
if (triggerCancel) {
@@ -87,10 +84,12 @@ export default function confirm(config: ModalFuncProps) {
}
}
- reactUnmount();
+ unmount(container).then(() => {
+ // Do nothing
+ });
}
- function render(props: any) {
+ function scheduleRender(props: any) {
clearTimeout(timeoutId);
/**
@@ -105,11 +104,9 @@ export default function confirm(config: ModalFuncProps) {
const dom = ;
- const reactRender = unstableSetRender();
-
- reactUnmount = reactRender(
+ render(
- {global.holderRender ? global.holderRender(dom) : dom}
+ {typeof global.holderRender === 'function' ? global.holderRender(dom) : dom}
,
container,
);
@@ -129,7 +126,7 @@ export default function confirm(config: ModalFuncProps) {
},
};
- render(currentConfig);
+ scheduleRender(currentConfig);
}
function update(configUpdate: ConfigUpdate) {
@@ -141,10 +138,10 @@ export default function confirm(config: ModalFuncProps) {
...configUpdate,
};
}
- render(currentConfig);
+ scheduleRender(currentConfig);
}
- render(currentConfig);
+ scheduleRender(currentConfig);
destroyFns.push(close);
diff --git a/components/notification/index.tsx b/components/notification/index.tsx
index 9e0cd433c6..93c5252cb2 100755
--- a/components/notification/index.tsx
+++ b/components/notification/index.tsx
@@ -1,8 +1,8 @@
import React, { useContext } from 'react';
+import { render } from '@rc-component/util/lib/React/render';
import { AppConfigContext } from '../app/context';
import ConfigProvider, { ConfigContext, globalConfig, warnContext } from '../config-provider';
-import { unstableSetRender } from '../config-provider/UnstableContext';
import type { ArgsProps, GlobalConfigProps, NotificationInstance } from './interface';
import PurePanel from './PurePanel';
import useNotification, { useInternalNotification } from './useNotification';
@@ -126,13 +126,10 @@ const flushNotificationQueue = () => {
// Delay render to avoid sync issue
act(() => {
- const reactRender = unstableSetRender();
-
- reactRender(
+ render(
{
const { instance, sync } = node || {};
-
Promise.resolve().then(() => {
if (!newNotification.instance && instance) {
newNotification.instance = instance;
diff --git a/docs/react/v5-for-19.en-US.md b/docs/react/v5-for-19.en-US.md
deleted file mode 100644
index c874b1d1fc..0000000000
--- a/docs/react/v5-for-19.en-US.md
+++ /dev/null
@@ -1,54 +0,0 @@
----
-order: 1
-title: React 19 Compatibility
-tag: New
----
-
-
-:::info{title="Compatibility Interface"}
-antd v5 is compatible with React 16 ~ 18 by default, and most features are also compatible with React 19. A few issues are listed below, and the following compatibility methods can be used for adaptation. This method and interface will be removed in v6.
-::::
-
-### React 19 Compatibility Issues
-
-Due to React 19 adjusting the export method of `react-dom`, antd cannot directly use the `ReactDOM.render` method. Therefore, using antd will encounter the following issues:
-
-- Wave effect does not work properly
-- `Modal`、`Notification`、`Message` and other components' static methods are invalid (hooks invocation methods are not affected).
-
-Therefore, you need to use a compatibility configuration to make antd work properly in React 19.
-
-### Compatibility Methods
-
-You can choose one of the following methods, and it is recommended to use the compatibility package first.
-
-#### Compatibility Package
-
-Install the compatibility package
-
-
-
-Import the compatibility package at the application entry
-
-```tsx
-import '@ant-design/v5-patch-for-react-19';
-```
-
-#### unstableSetRender
-
-Once again, please use the compatibility package first. Only for special scenarios such as umd, micro-applications, etc., use the `unstableSetRender` method. `unstableSetRender` is a low-level registration method that allows developers to modify the rendering method of ReactDOM. Write the following code at the entry of your application:
-
-```js
-import { unstableSetRender } from 'antd';
-import { createRoot } from 'react-dom/client';
-
-unstableSetRender((node, container) => {
- container._reactRoot ||= createRoot(container);
- const root = container._reactRoot;
- root.render(node);
- return async () => {
- await new Promise((resolve) => setTimeout(resolve, 0));
- root.unmount();
- };
-});
-```
diff --git a/docs/react/v5-for-19.zh-CN.md b/docs/react/v5-for-19.zh-CN.md
deleted file mode 100644
index 7615cbb4e2..0000000000
--- a/docs/react/v5-for-19.zh-CN.md
+++ /dev/null
@@ -1,54 +0,0 @@
----
-order: 1
-title: React 19 兼容
-tag: New
----
-
-
-:::info{title="兼容接口"}
-antd v5 默认兼容 React 16 ~ 18 版本,绝大多数功能也兼容于 React 19 版本。少数问题如下所示,可使用以下兼容方法进行适配。该方式以及接口将在 v6 被移除。
-:::
-
-### React 19 兼容问题
-
-由于 React 19 调整了 `react-dom` 的导出方式,导致 antd 无法直接使用 `ReactDOM.render` 方法。因而使用 antd 会遇到以下问题:
-
-- 波纹特效无法正常工作
-- `Modal`、`Notification`、`Message` 等组件的静态方法无效(hooks 调用方式不受影响)
-
-因而需要通过兼容配置,使 antd 在 React 19 中正常工作。
-
-### 兼容方式
-
-以下方法任选其一,其中优先推荐使用兼容包。
-
-#### 兼容包
-
-安装兼容包
-
-
-
-在应用入口处引入兼容包
-
-```tsx
-import '@ant-design/v5-patch-for-react-19';
-```
-
-#### unstableSetRender
-
-再次提醒,默认情况下,请优先使用兼容包。除非对于 umd、微应用等特殊场景,才使用 `unstableSetRender` 方法。`unstableSetRender` 为底层注册方法,允许开发者修改 ReactDOM 的渲染方法。在你的应用入口处写入:
-
-```js
-import { unstableSetRender } from 'antd';
-import { createRoot } from 'react-dom/client';
-
-unstableSetRender((node, container) => {
- container._reactRoot ||= createRoot(container);
- const root = container._reactRoot;
- root.render(node);
- return async () => {
- await new Promise((resolve) => setTimeout(resolve, 0));
- root.unmount();
- };
-});
-```
diff --git a/package.json b/package.json
index 0e942ef194..cd472e07c0 100644
--- a/package.json
+++ b/package.json
@@ -161,7 +161,6 @@
"@ant-design/compatible": "^5.1.4",
"@ant-design/happy-work-theme": "^1.0.1",
"@ant-design/tools": "^19.0.3",
- "@ant-design/v5-patch-for-react-19": "^1.0.3",
"@ant-design/x": "^1.6.1",
"@antfu/eslint-config": "^5.4.1",
"@antv/g6": "^4.8.25",
@@ -225,7 +224,7 @@
"ali-oss": "^6.23.0",
"antd-img-crop": "^4.27.0",
"antd-style": "4.0.0-alpha.1",
- "antd-token-previewer": "3.0.0-alpha.1",
+ "antd-token-previewer": "3.0.0-alpha.3",
"axios": "^1.12.2",
"chalk": "^5.6.2",
"cheerio": "^1.1.2",
diff --git a/tests/__snapshots__/index.test.ts.snap b/tests/__snapshots__/index.test.ts.snap
index d9136a751c..ccf24ac5b5 100644
--- a/tests/__snapshots__/index.test.ts.snap
+++ b/tests/__snapshots__/index.test.ts.snap
@@ -74,7 +74,6 @@ exports[`antd dist files exports modules correctly 1`] = `
"message",
"notification",
"theme",
- "unstableSetRender",
"version",
]
`;