Interaction tracking records nothing: the babel plugin's injected import makes Metro bundle a second copy of the SDK, so DdBabelInteractionTracking.config is set on a different class than instrumented handlers call
Nobody has claimed this yet.
Assessment
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Newbie friendliness
- 48/100
- Issue type
- Bug
- Clarity
- Mostly clear
- Activity status
- Active
- Tech stack
- babel, react-native, typescript
Research direction
Start by reproducing the Expo bundle and source-map counts, then read packages/core/package.json, packages/core/src/DdSdkReactNative.tsx around line 529, and packages/core/src/rum/instrumentation/interactionTracking/DdBabelInteractionTracking.ts around lines 36 and 137. Inspect the Babel plugin's injected import and verify which build it resolves to. Done means the app and instrumented handlers use one SDK copy and a Pressable emits an action without the Metro workaround.
Written by the indexing model from the issue text.
Description
Describe the bug
With @datadog/mobile-react-native-babel-plugin enabled, Metro bundles @datadog/mobile-react-native twice: 75 modules from lib/module and 75 from lib/commonjs. packages/core/package.json keeps the builds separate (exports["."] maps import to ./lib/module/index.js and require to ./lib/commonjs/index.js), so DdBabelInteractionTracking becomes two classes with independent static state.
enableFeatures() assigns DdBabelInteractionTracking.config (DdSdkReactNative.tsx:529) on the copy the app imported. Instrumented handlers call getInstance().wrapRumAction() on the copy the plugin's injected import resolved to, where config is still the default trackInteractions: false (DdBabelInteractionTracking.ts:36). wrapRumAction reads that guard at L137, skips addAction, and falls through to func?.(...args).
No action is ever recorded. Views stay at action.count: 0, no @type:action events are produced, nothing errors or warns at any verbosity, and press handlers behave normally. Views, Session Replay, long tasks and crash reporting keep working, because those run through the configured copy.
A broken setup is therefore indistinguishable from a working one. Every obvious check reads healthy, because it reads the configured copy: globalThis.__DD_RN_BABEL_PLUGIN_ENABLED__ is true, config.trackInteractions is true, the instance has a ddRum, and calling wrapRumAction directly records an action correctly. Metro caching, flag load order, NativeWind's JSX runtime and tracking consent all look fine for the same reason.
Reproduction steps
Expo SDK 56 / RN 0.85.3, SDK and plugin installed per the Expo setup docs, trackInteractions: true, DatadogProvider at the root, SDK imported with ESM.
// babel.config.js
module.exports = { presets: ['babel-preset-expo'],
plugins: ['@datadog/mobile-react-native-babel-plugin'] };
- Build Release, tap any
Pressable. The handler runs, no action event is emitted. - Count the copies:
npx expo export:embed --platform ios --dev false \
--bundle-output out.js --sourcemap-output out.map
node -e "const s=require('./out.map').sources;
console.log('module :', s.filter(x=>x.includes('mobile-react-native/lib/module/')).length);
console.log('commonjs:', s.filter(x=>x.includes('mobile-react-native/lib/commonjs/')).length)"
# module: 75 commonjs: 75
- Remove the plugin from
babel.config.jsand re-bundle:module: 75, commonjs: 0. - Patch
wrapRumActionon the instance from the app's own import insideonInitialization. A direct call records an action; a real press never reaches the patched method, while the handler still fires.
SDK logs
None. wrapRumAction returns at its trackInteractions guard before any logging or telemetry, so SdkVerbosity.DEBUG shows nothing and no BabelActionTrack telemetry fires. No output distinguishes this from "no interactions occurred".
Expected behavior
Instrumented handlers and enableFeatures() should share one DdBabelInteractionTracking. Any of:
- Don't hold
configandinstancein per-build module statics, where a duplicated module silently disables the feature. - Have the plugin's injected import resolve to the build the host app imports.
- Warn when
wrapRumActionruns against an instance whoseconfigwas never assigned, or whengetInstance()is created without addRum.
The third alone would make this diagnosable without inspecting the bundle's module graph.
Affected SDK versions
2.14.7, plugin 2.14.9. 3.6.0 ships the same exports map and the same static-config pattern.
Latest working SDK version
None known. The trigger is the dual-build layout plus the plugin's injected import, not a regression.
Did you confirm if the latest SDK version fixes the bug?
No. Not verified at runtime on 3.x. By inspection, 3.6.0's packages/core/package.json has the same import/require split and config is still a class static, so we expect it to reproduce.
Integration Methods
NPM
React Native Version
0.85.3 (Expo SDK 56.0.13)
Platform
iOS (iPhone 17 Pro simulator, iOS 26.1, Release). The mechanism is JS bundling, so Android should behave the same.
Package.json Contents
"expo": "~56.0.13", "react-native": "0.85.3", "react": "19.2.3",
"@datadog/mobile-react-native": "^2.14.0",
"@datadog/mobile-react-native-babel-plugin": "^2.14.0",
"@datadog/mobile-react-native-session-replay": "^2.14.0",
"nativewind": "^4.2.6"
Other relevant information
| config | lib/module |
lib/commonjs |
actions |
|---|---|---|---|
| plugin disabled | 75 | 0 | n/a |
| plugin enabled | 75 | 75 | none |
plugin enabled, transformer.experimentalImportSupport: true |
75 | 75 | none |
plugin enabled, resolver pinned to lib/module |
75 | 0 | yes |
plugin enabled, app imports SDK with require |
0 | 75 | n/a |
The last row shows either build works in isolation. Matching the app's import style to the plugin's require also collapses the graph to one copy. We did not use that approach: require returns any so the SDK types are lost unless the import is split, it trips @typescript-eslint/no-require-imports, and it only works while Metro defaults experimentalImportSupport to false. If that default flips, the plugin emits import, resolves to lib/module, and tracking breaks again with no signal.
Ruled out: forcing ESM does not help, so we could not pin the exact resolution mechanism, only that the plugin's injected import is needed to produce the second copy. Removing our custom babelTransformerPath still gives 75/75. A cold Metro cache produces a byte-identical bundle. @datadog/mobile-react-native-session-replay resolves to its own src/*.ts and does not import core, so it is not a second entry point.
Workaround in metro.config.js:
const datadogRoot = path.dirname(require.resolve("@datadog/mobile-react-native/package.json"));
const datadogEntry = path.join(datadogRoot, "lib/module/index.js");
resolveRequest: (context, moduleName, platform) =>
moduleName === "@datadog/mobile-react-native"
? { type: "sourceFile", filePath: datadogEntry }
: context.resolveRequest(context, moduleName, platform)
Actions record immediately after this and Session Replay is unaffected.
- Dominant language
- TypeScript
- Stars
- 146
- Forks
- 63
- Avg merge
- 1d 10h
- Merged PRs (30d)
- 11
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from DataDog/dd-sdk-reactnative
-
bug
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
DataDog/dd-sdk-reactnative#1382 · 5 comments ·
-
Difficulty 3/5 1-2 days Newbie friendliness 76/100
DataDog/dd-sdk-reactnative#1426 ·
-
bug
DataDog/dd-sdk-reactnative#1412 · 1 comment · 1 assignee ·
-
bug
Difficulty 3/5 1-2 days Newbie friendliness 76/100
DataDog/dd-sdk-reactnative#1353 · 1 comment ·
-
enhancement
Difficulty 3/5 1-2 days Newbie friendliness 58/100
DataDog/dd-sdk-reactnative#1338 · 2 comments ·
All issues in DataDog/dd-sdk-reactnative
Similar issues
-
S: triage
Difficulty 1/5 Under an hour Newbie friendliness 85/100
-
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
-
fix(errors): EHOSTUNREACH from a happy-eyeballs connect is reported as a resolver error (STAMP-80) Open
Difficulty 2/5 1-3 hours Newbie friendliness 90/100
snapshot-labs/stamp#666 ·
-
bug
Difficulty 2/5 1-3 hours Newbie friendliness 75/100
GauravKarakoti/SecureFlow#1070 · 1 comment ·
-
comp/desktop P3 type/bug
Difficulty 1/5 Under an hour Newbie friendliness 92/100
NousResearch/hermes-agent#118866 ·