diff --git a/.changelog/5971-fix-react-19-follow-ups.md b/.changelog/5971-fix-react-19-follow-ups.md
new file mode 100644
index 0000000000..f81937d90d
--- /dev/null
+++ b/.changelog/5971-fix-react-19-follow-ups.md
@@ -0,0 +1,3 @@
+### Fixed
+
+- Fixed the IIFE build of the Fluent theme crashing on load with React 19, in PR [#5971](https://github.com/microsoft/BotFramework-WebChat/pull/5971), by [@OEvgeny](https://github.com/OEvgeny)
diff --git a/__tests__/html2/react19/codeBlockCopyButton.html b/__tests__/html2/react19/codeBlockCopyButton.html
new file mode 100644
index 0000000000..44caa881a2
--- /dev/null
+++ b/__tests__/html2/react19/codeBlockCopyButton.html
@@ -0,0 +1,92 @@
+
+
+
+ React 19: renders the copy button of code blocks
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/__tests__/html2/react19/codeBlockCopyButton.html.snap-1.png b/__tests__/html2/react19/codeBlockCopyButton.html.snap-1.png
new file mode 100644
index 0000000000..f931af3deb
Binary files /dev/null and b/__tests__/html2/react19/codeBlockCopyButton.html.snap-1.png differ
diff --git a/__tests__/html2/react19/fluentTheme.iife.html b/__tests__/html2/react19/fluentTheme.iife.html
new file mode 100644
index 0000000000..d94578f86f
--- /dev/null
+++ b/__tests__/html2/react19/fluentTheme.iife.html
@@ -0,0 +1,80 @@
+
+
+
+ React 19: the IIFE build of the Fluent theme sends a message and shows the reply
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/__tests__/html2/react19/fluentTheme.iife.html.snap-1.png b/__tests__/html2/react19/fluentTheme.iife.html.snap-1.png
new file mode 100644
index 0000000000..ca3f67cbf3
Binary files /dev/null and b/__tests__/html2/react19/fluentTheme.iife.html.snap-1.png differ
diff --git a/__tests__/html2/react19/renderWebChat.esm.html b/__tests__/html2/react19/renderWebChat.esm.html
index ac79ff7a4d..708d95f68c 100644
--- a/__tests__/html2/react19/renderWebChat.esm.html
+++ b/__tests__/html2/react19/renderWebChat.esm.html
@@ -8,7 +8,7 @@
diff --git a/packages/bundle/src/renderWebChat.spec.tsx b/packages/bundle/src/renderWebChat.spec.tsx
index 1ca156f321..b569dfcada 100644
--- a/packages/bundle/src/renderWebChat.spec.tsx
+++ b/packages/bundle/src/renderWebChat.spec.tsx
@@ -44,6 +44,18 @@ test('Root should be rendered by root.render() instead of ReactDOM.render()', ()
expect(render).not.toHaveBeenCalled();
});
+test('Error from root.render() should be thrown synchronously to the host', () => {
+ // React throws this when rendering with a root after `root.unmount()`.
+ const root = {
+ render: jest.fn(() => {
+ throw new Error('Cannot update an unmounted root.');
+ })
+ };
+
+ expect(() => renderWebChat(ReactWebChat, {}, root)).toThrow('Cannot update an unmounted root.');
+ expect(root.render).toHaveBeenCalledTimes(1);
+});
+
test('Element on React 19 should throw an error asking for a root', () => {
// React 19 removed `ReactDOM.render()`.
Object.assign(ReactDOM, { render: undefined });
@@ -55,5 +67,5 @@ test('Element on React 19 should throw an error asking for a root', () => {
test('Neither a container nor a root should throw', () =>
expect(() => renderWebChat(ReactWebChat, {}, {} as unknown as HTMLElement)).toThrow(
- 'renderWebChat() expects an HTMLElement or a root from createRoot().'
+ 'renderWebChat() expects an HTMLElement or a root from ReactDOM.createRoot().'
));
diff --git a/packages/bundle/src/renderWebChat.tsx b/packages/bundle/src/renderWebChat.tsx
index d1e9230573..9f12077a9d 100644
--- a/packages/bundle/src/renderWebChat.tsx
+++ b/packages/bundle/src/renderWebChat.tsx
@@ -1,6 +1,7 @@
+import { reactNode } from '@msinternal/botframework-webchat-react-valibot';
import React, { type ComponentType, type ReactNode } from 'react';
import ReactDOM from 'react-dom';
-import { custom, function_, is, object, picklist } from 'valibot';
+import { args, custom, function_, is, object, picklist, pipe, tuple } from 'valibot';
/** A root from `createRoot()` of "react-dom/client". */
type RenderWebChatRoot = { render(children: ReactNode): void };
@@ -26,13 +27,16 @@ const DOCUMENT_NODE = 9;
const DOCUMENT_FRAGMENT_NODE = 11;
// Containers `ReactDOM.render()` accepts: element, document and document fragment, by `nodeType` to work across realms.
-// Checked before `renderSchema`: some custom elements (e.g. Lit) have their own `render()` method.
+// Checked before `reactRootSchema`: some custom elements (e.g. Lit) have their own `render()` method.
const containerSchema = custom>(value =>
is(object({ nodeType: picklist([DOCUMENT_FRAGMENT_NODE, DOCUMENT_NODE, ELEMENT_NODE]) }), value)
);
-// Both `ReactDOM.render()` (React 18 or earlier) and `root.render()`.
-const renderSchema = object({ render: function_() });
+// `ReactDOM.render(children, container)`, React 18 or earlier.
+const legacyReactDOMSchema = object({ render: pipe(function_(), args(tuple([reactNode(), containerSchema]))) });
+
+// `root.render(children)`, root from `createRoot()` of "react-dom/client".
+const reactRootSchema = object({ render: pipe(function_(), args(tuple([reactNode()]))) });
export default function renderWebChat(
ReactWebChat: ComponentType,
@@ -43,7 +47,7 @@ export default function renderWebChat(
if (is(containerSchema, elementOrRoot)) {
// React 19 or later removed `ReactDOM.render()`: Web Chat does not import "react-dom/client", the host creates the root.
- if (!is(renderSchema, ReactDOM)) {
+ if (!is(legacyReactDOMSchema, ReactDOM)) {
throw new Error(
'botframework-webchat: renderWebChat() on React 19 or later needs a root: pass createRoot(element) from "react-dom/client" instead of the element.'
);
@@ -51,11 +55,13 @@ export default function renderWebChat(
// React 16 to 18, and Preact. Also the IIFE build, which bundles React DOM 16.8.6 unless `window.ReactDOM` is set.
ReactDOM.render(children, elementOrRoot);
- } else if (is(renderSchema, elementOrRoot)) {
+ } else if (is(reactRootSchema, elementOrRoot)) {
// The host created the root: render with it on any React version, it is the host's to unmount.
elementOrRoot.render(children);
} else {
- throw new Error('botframework-webchat: renderWebChat() expects an HTMLElement or a root from createRoot().');
+ throw new Error(
+ 'botframework-webchat: renderWebChat() expects an HTMLElement or a root from ReactDOM.createRoot().'
+ );
}
}
diff --git a/packages/fluent-theme/src/external.umd/react-dom.ts b/packages/fluent-theme/src/external.umd/react-dom.ts
new file mode 100644
index 0000000000..ea199e65a0
--- /dev/null
+++ b/packages/fluent-theme/src/external.umd/react-dom.ts
@@ -0,0 +1,4 @@
+///
+
+// The React DOM of the page, as with "react": a bundled React DOM cannot initialize with another React, such as React 19.
+module.exports = (globalThis as any).ReactDOM;
diff --git a/packages/fluent-theme/tsup.config.ts b/packages/fluent-theme/tsup.config.ts
index 291ddd36e2..d665a89a7b 100644
--- a/packages/fluent-theme/tsup.config.ts
+++ b/packages/fluent-theme/tsup.config.ts
@@ -16,6 +16,12 @@ const umdResolvePlugin = {
path: join(fileURLToPath(import.meta.url), '../src/external.umd/react.ts')
}));
+ // ESBuild use Go regular expressions and does not understand Unicode flag.
+ // eslint-disable-next-line require-unicode-regexp
+ build.onResolve({ filter: /^react-dom$/ }, () => ({
+ path: join(fileURLToPath(import.meta.url), '../src/external.umd/react-dom.ts')
+ }));
+
// ESBuild use Go regular expressions and does not understand Unicode flag.
// eslint-disable-next-line require-unicode-regexp
build.onResolve({ filter: /^botframework-webchat-api$/ }, () => ({