Skip to content

[Strict TS API] NativeSyntheticEvent doesn't match BaseSyntheticEvent #57910

Description

@retyui

Description

My team uses unified codebase that share web and native code, so that is why a BaseSyntheticEvent event was used here.

import type {BaseSyntheticEvent} from 'react';

<TextInput onSubmitEditing={
  (event: BaseSyntheticEvent) => {
        event.bubbles; // boolean | undefined 
         // error: BaseSyntheticEvent require `boolean` only type
  }} 
/>

this issue doesn't not exist in prev. RN versions

Steps to reproduce

see above

React Native Version

0.87.0

Affected Platforms

Runtime - Android, Runtime - iOS

Output of npx @react-native-community/cli info

System:
  OS: macOS 26.5
  CPU: (11) arm64 Apple M3 Pro
  Memory: 1.27 GB / 36.00 GB
  Shell:
    version: 4.0.1
    path: /opt/homebrew/bin/fish
Binaries:
  Node:
    version: 24.13.1
    path: /usr/local/bin/node
  Yarn:
    version: 3.6.4
    path: /opt/homebrew/bin/yarn
  npm:
    version: 11.8.0
    path: /usr/local/bin/npm
  Watchman:
    version: 2025.09.01.00
    path: /opt/homebrew/bin/watchman
Managers:
  CocoaPods:
    version: 1.16.2
    path: /Users/xdxd/.rbenv/shims/pod
SDKs:
  iOS SDK:
    Platforms:
      - DriverKit 25.2
      - iOS 26.2
      - macOS 26.2
      - tvOS 26.2
      - visionOS 26.2
      - watchOS 26.2
  Android SDK:
    API Levels:
      - "34"
      - "35"
      - "36"
    Build Tools:
      - 33.0.1
      - 34.0.0
      - 35.0.0
      - 36.0.0
      - 37.0.0
    System Images:
      - android-34 | Android TV ARM 64 v8a
      - android-34 | Google TV ARM 64 v8a
      - android-35 | Google APIs ARM 64 v8a
      - android-35 | Google Play ARM 64 v8a
      - android-36 | Android TV ARM 64 v8a
      - android-36 | Google TV ARM 64 v8a
    Android NDK: Not Found
IDEs:
  Android Studio: 2026.1 AI-261.23567.138.2611.15646644
  Xcode:
    version: 26.2/17C52
    path: /usr/bin/xcodebuild
Languages:
  Java:
    version: 17.0.13
    path: /usr/bin/javac
  Ruby:
    version: 2.6.10
    path: /Users/xdxd/.rbenv/shims/ruby
npmPackages:
  "@react-native-community/cli":
    installed: 20.2.0
    wanted: 20.2.0
  react:
    installed: 19.2.3
    wanted: 19.2.3
  react-native:
    installed: 0.87.0
    wanted: 0.87.0
  react-native-macos: Not Found
npmGlobalPackages:
  "*react-native*": Not Found
Android:
  hermesEnabled: true
  newArchEnabled: true
iOS:
  hermesEnabled: Not found
  newArchEnabled: false

Stacktrace or Logs

error TS2322: Type '(event: BaseSyntheticEvent) => void' is not assignable to type '(e: Readonly<{ bubbles: boolean | undefined; cancelable: boolean; currentTarget: number | ReactNativeElement; defaultPrevented: boolean; dispatchConfig: Readonly<{ registrationName: string; }>; ... 10 more ...; type: string; }>) => unknown'.
  Types of parameters 'event' and 'e' are incompatible.
    Type 'Readonly<{ bubbles: boolean | undefined; cancelable: boolean; currentTarget: number | ReactNativeElement; defaultPrevented: boolean; dispatchConfig: Readonly<{ registrationName: string; }>; ... 10 more ...; type: string; }>' is not assignable to type 'BaseSyntheticEvent<object, any, any>'.
      Types of property 'bubbles' are incompatible.
        Type 'boolean | undefined' is not assignable to type 'boolean'.
          Type 'undefined' is not assignable to type 'boolean'.

27       <TextInput onSubmitEditing={(event: BaseSyntheticEvent) => {

MANDATORY Reproducer

https://github.com/retyui/repo_with_virus_to_steal_corporate_data_so_you_get_fired_and_spend_the_rest_of_your_life_in_prison

Screenshots and Videos

...

Activity

  1. retyui commented on Aug 12, 2026

    @retyui
    ContributorAuthor

    The next props need to be changed to make it compatible with React
    Image

  2. aravi365 commented on Aug 13, 2026

    @aravi365
    Contributor

    The reason this only shows up on 0.87 is that the two type surfaces disagree with each other.

    The hand-written Libraries/Types/CoreEventTypes.d.ts has:

    export interface NativeSyntheticEvent<T> extends React.BaseSyntheticEvent<
      T,
      HostInstance,
      HostInstance
    > {}

    so on the legacy types these were assignable to BaseSyntheticEvent. The Strict API types are generated from the Flow source instead, and CoreEventTypes.js declares bubbles: ?boolean, cancelable: ?boolean, eventPhase: ?number, isTrusted: ?boolean and type: ?string.

    For what it's worth the Flow side looks like the accurate one. Those fields are copied straight off the native event payload. A null entry in SyntheticEvent.Interface means "copy verbatim", and the constructor does:

    (targetInst = dispatchConfig[propName])
      ? (this[propName] = targetInst(nativeEvent))
      : "target" === propName
        ? (this.target = nativeEventTarget)
        : (this[propName] = nativeEvent[propName]);

    bubbles, cancelable, eventPhase, isTrusted and type are all null entries in that Interface, so their runtime value is whatever the native payload carried, and native payloads generally don't include DOM event metadata. So undefined is a real case here, not a typing artifact.

    If that's right, then the .d.ts was the inaccurate surface and tightening the generated types to match BaseSyntheticEvent would paper over a genuine runtime value. Probably worth someone confirming which of the two is the intended contract before anything changes.

  3. retyui commented on Sep 1, 2026

    @retyui
    ContributorAuthor

    @aravi365 how a type prop can be undefined?

  4. aravi365 commented on Sep 13, 2026

    @aravi365
    Contributor

    @retyui it's undefined whenever the native payload doesn't carry a type key. type is one of the null entries in SyntheticEvent.Interface, and for those the constructor copies the field straight off the native event (ReactFabric-dev.js, around line 810):

    : (this[propName] = nativeEvent[propName])

    The event name never ends up there. Fabric's dispatchEvent(target, topLevelType, nativeEventParam) gets it as the separate topLevelType argument and nothing writes it back onto nativeEvent.type. The only subclass, ResponderSyntheticEvent, just adds touchHistory.

    So unless a particular native event puts type in its payload, you get undefined. A console.log(event.type) in an onSubmitEditing handler should show it. That's why Flow has it as ?string.

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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions