`onChange` on Polaris web components still fires twice on React 19

This was reported in before and marked fixed in October 2025, but I still see it today with the live polaris-2.js and React 19.3.1.

<s-checkbox label="Click me once" onChange={() => console.log("CALLED")} />

One click logs “CALLED” twice. On React 18.3.1 it logs once. s-choice-list behaves the same way.

Here’s a jsfiddle.

Lowercase onchange fires once, but @shopify/polaris-types 2.0.0 only accepts camelCase onChange in JSX, so TypeScript rejects it. Which spelling is intended for React 19?

Follow-up: lowercase onchange isn’t a workaround either. With SSR + hydrateRoot (React Router, Remix, Next), React 19 attaches no listeners for on* props on custom elements during hydration, and the shim skips onchange because the element has that property. So it gets 0 calls on hydrated elements.

What I measured on React 19.3.1:

client render hydrated
onChange 2 1, then 2 once the shim processes queued elements
onchange 1 0
onInput 1 1

Suggested fix from Claude: React 19 dispatches onChange for custom elements itself, on both client-rendered and hydrated elements. So under React 19 the shim could stop adding its own change listener, i.e. drop change from the always-listen set (change, error, load, toggle) when it detects React 19. Then camelCase onChange, the spelling polaris-types documents, would fire once everywhere.

Related: elements upgraded before hydration seem to be queued until the next document.addEventListener call, so on a hydrated page onDismiss showed 0 calls until then. Processing that queue right after hydration would make custom events reliable from the first interaction.