Skip to content

Support React 19 - #5955

Open
Eugene (OEvgeny) wants to merge 18 commits into
mainfrom
OEvgeny/fix-react-19-support
Open

Eugene (OEvgeny) wants to merge 18 commits into
mainfrom
OEvgeny/fix-react-19-support

Conversation

@OEvgeny

@OEvgeny Eugene (OEvgeny) commented Oct 22, 2026 •

Copy link
Copy Markdown
Collaborator

Fixes #5552
Fixes #5850

Changelog Entry

Added

  • Added #5552 and #5850, support for React 19, in PR #5955, by @OEvgeny
  • 👷🏻 Pull request validation: added type definitions tests with @types/react@19, in PR #5955, by @OEvgeny

Removed

  • Components: removed the remaining propTypes and the prop-types dependency, React 19 ignores them, in PR #5955, by @OEvgeny

Fixed

  • Fixed Adaptive Cards submitting empty inputs in Strict Mode on React 19, in PR #5955, by @OEvgeny
  • Fixed Composer of the full bundle not calling a children function with extraStyleSet, in PR #5955, by @OEvgeny

Description

React 19 ran since #5961, #5963 and #5980, but it was not declared, tested in development or Strict Mode, or type-checked. This declares it, tests it, fixes what the tests found, and removes the remaining propTypes.

Design

  • Adaptive Cards: the render result is reused while its inputs are unchanged; rendering in an effect instead delayed the card by one commit and broke the live region.
  • The "Removed" entry is not breaking: the propTypes were on internal components, none is exported.

Specific Changes

  • docs/HOSTING.md: React 16.8.6 to 19 supported

  • AdaptiveCardRenderer.tsx: one render per inputs; react19/strictMode.adaptiveCard.submit.html (React 18: strictMode/adaptiveCard.submit.html): posted value kept

  • react19/strictMode.esm.html: React 19 dev, Strict Mode: send (snapshot 1), carousel (2), suggested action (3), card submit (4)

  • react19/renderWebChat.esm.html: real React 19

  • Components.Composer: children typed as nodes, it never called a function; FullComposer calls a children function with extraStyleSet

  • Type definitions test: also with @types/react@19

  • Removed 37 propTypes, prop-types from 4 packages

  • I have added tests and executed them locally

  • I have added .changelog/<PR#>-<slug>.md

  • I have updated documentation

Review Checklist

This section is for contributors to review your work.

  • Accessibility reviewed (tab order, content readability, alt text, color contrast)
  • Browser and platform compatibilities reviewed
  • CSS styles reviewed (minimal rules, no z-index)
  • Documents reviewed (docs, samples, live demo)
  • Internationalization reviewed (strings, unit formatting)
  • package.json and package-lock.json reviewed
  • Security reviewed (no data URIs, check for nonce leak)
  • Tests reviewed (coverage, legitimacy)

Eugene Olonov added 6 commits October 21, 2026 19:32
…eparate React root

React 19 removed ReactDOM.render() and unmountComponentAtNode(). The copy button is now rendered by createPortal() in the React tree of Web Chat, by the innermost scope (focus trap of the activity, or the composer) containing the element, so keyboard and focus events still reach the activity.
React 16 to 18 and Preact keep ReactDOM.render(). On React 19, the ESM build imports react-dom/client through the import map; other builds throw a clear error.
The IIFE build evaluated its bundled React DOM 16 even when window.ReactDOM was set, which fails with React 19 in window.React. The ESM tests are skipped until react-film no longer imports render() from react-dom (spyip/react-film#110).
Resolve the conflict in CustomElementsComposer.tsx by keeping both changes:

- #5951: the module-level `registerCustomElement()` with its `WeakMap` from class to tag name, so each class is defined once with its own tag and Strict Mode can call `useMemo()` twice.
- This branch: the per-composer `CustomElementPortalRegistry`, passed to `useReactCodeBlockCopyButtonClass()`, put in the context and rendered by the root `<CustomElementPortalScope />`.

The copy button class is created per registry, so each composer still gets its own class and tag; the registry discarded by Strict Mode's second `useMemo()` call never defines a class.
#5859 is fixed on main (#5951): with an import map shim that adds the `render` export react-film needs, the test passes.
@OEvgeny

Copy link
Copy Markdown
Collaborator Author

We need to support backward compatibility down to React 16

@OEvgeny
Eugene (OEvgeny) marked this pull request as ready for review October 22, 2026 06:47
Copilot AI balanced review requested due to automatic review settings October 22, 2026 06:47

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@OEvgeny Eugene (OEvgeny) left a comment •

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't request changes on mine PR, but I'd definitely do it in this case.

@OEvgeny
Eugene (OEvgeny) marked this pull request as draft October 22, 2026 07:14
@OEvgeny
Eugene (OEvgeny) marked this pull request as ready for review October 22, 2026 07:36
Replaces the portal approach: the copy button is an HTMLElement subclass that builds a native <button>
with the same classes, ARIA and test ID, so it needs no React root (React 19 removed render()) and its
events bubble through the DOM to the activity, e.g. ESCAPE.

Removes CustomElementPortalRegistry, CustomElementPortalScope, the FocusTrap scope and wrapAsCustomElement;
CodeBlock passes the localized strings as attributes again, as on main.
Links spyip/react-film#110 in full in the skipped React 19 ESM tests.
- `createRootRequiredError()` is thrown explicitly: synchronously in the npm build on React 19, where "react-dom/client" cannot be imported.
- Comments name the build of each branch: IIFE with the bundled React DOM 16.8.6, `window.ReactDOM` 19, static (ESM) build, npm build.
- Static build: errors after `renderWebChat()` returned are logged with `console.error()` instead of an unhandled rejection.
"react-dom" exports createRoot() only through "react-dom/client"; a react-dom
object with createRoot() could only come from a hand-merged window.ReactDOM in
the IIFE build, which was untested and undocumented.
@OEvgeny Eugene (OEvgeny) changed the title Support React 19: render the code block copy button through a portal and use createRoot when render is gone Support React 19: render the code block copy button as a plain custom element and use createRoot() when render() is gone Oct 22, 2026
@OEvgeny

Copy link
Copy Markdown
Collaborator Author

Draft until we figure the react-film approach

Eugene (OEvgeny) added a commit that referenced this pull request Oct 28, 2026
#5971)

* Split renderWebChat()'s render schema into legacyReactDOMSchema and reactRootSchema

* Use the React DOM of the page in the IIFE build of the Fluent theme

The IIFE build bundled React DOM 16.8.6 (through renderWebChat() of botframework-webchat) and evaluated it on load against window.React, which crashes with React 19. Like "react", "react-dom" now resolves to window.ReactDOM; the bundled copy was only used by renderWebChat(), which the IIFE build does not export.

* Add a React 19 test for the IIFE build of the Fluent theme

* Remove react-dom/client from the baseline import map in HOSTING.md

static/react-dom.baseline/client.js is never built (React 16 has no react-dom/client), and renderWebChat() does not import react-dom/client: on React 19, the host passes a root from createRoot().

* Test that renderWebChat() throws an error from root.render() synchronously

* Add a React 19 test for the copy button of code blocks with the IIFE build

Ported from #5955: the page creates its own root with createRoot() and renders <ReactWebChat> with React 19.

* Name ReactDOM.createRoot() in the error of renderWebChat() for an invalid container

* Say what keeps the ESM renderWebChat() test on emulated React 19: react-film imports render() by name

* Expect the version of ReactDOM in the React 19 tests

* Spy on root.render() with jest-mock in the React 18 renderWebChat() test

* Add the changelog fragment

---------

Co-authored-by: Eugene Olonov <eugeneolonov@microsoft.com>
Eugene Olonov added 6 commits October 30, 2026 10:20
The earlier changes of this branch landed on main through #5961 and #5963 (superseded), so the tree is now main's.
React 19 works since #5961, #5963 and the react-film bump in #5980: add the changelog entry, and stop calling React 18 the latest supported version in HOSTING.md.
- renderWebChat.esm.html: use React 19 instead of React 18 without ReactDOM.render(), the static build loads on React 19 since #5980.
- strictMode.esm.html: send a message, flip a carousel and click a suggested action on React 19 development in Strict Mode, without any console.error.
… keeps the typed inputs

The card is rendered in useMemo(). In Strict Mode, React 19 calls it twice and keeps the first result, while the card reads its inputs from the DOM of the second render: Action.Submit sent empty values. The result is now reused when the inputs did not change.
React 19 ignores propTypes, and TypeScript types check the props. None of these components is exported, and no published file imports prop-types. Props that propTypes typed before are now readonly, as react/prefer-read-only-props asks.
@OEvgeny Eugene (OEvgeny) changed the title Support React 19: render the code block copy button as a plain custom element and use createRoot() when render() is gone Support React 19 Oct 30, 2026
Eugene Olonov added 3 commits October 30, 2026 14:18
- useRef() needs an argument with @types/react@19.
- Composer of the component package renders children as nodes: its props no longer take the render function of the API Composer, which only type-checked on @types/react@16, where ReactNode includes {}.
- Composer of the full bundle calls a children function with extraStyleSet, as its typings say, instead of passing the function to Composer, which cannot render it.
CI type-checked only with @types/react@16, the baseline. The new step installs @types/react@19 in every workspace, since packages that declare @types/react@16 would otherwise keep their own copy. The tsd tests now create the elements instead of calling ReactDOM.render(), which @types/react-dom@19 no longer has.
@OEvgeny
Eugene (OEvgeny) marked this pull request as ready for review October 31, 2026 00:41

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

4 participants