Skip to content

[internal] add feature flag to fix event handle API in Activity - #35562

Open
rickhanlonii wants to merge 1 commit into
react:mainfrom
rickhanlonii:rh/ff-fix-event-api-activity
Open

[internal] add feature flag to fix event handle API in Activity#35562
rickhanlonii wants to merge 1 commit into
react:mainfrom
rickhanlonii:rh/ff-fix-event-api-activity

Conversation

@rickhanlonii

Copy link
Copy Markdown
Contributor

Noticed this TODO was never implemented, so this implements support for Activity inside the deprecated, never shipping enableCreateEventHandleAPI feature used internally until we remove the flag.

Also adds a bunch of test for this API with Activity and LegacyHidden, with inline gates for the differences with the flags.

Below is the claude summary.

Details

Fix Event Handle API blur events for Activity

Problem

The beforeblur and afterblur events from the createEventHandle API were not firing when a focused element was hidden using <React.Activity>. This was because the blur check in commitBeforeMutationEffectsOnFiber only handled SuspenseComponent, not OffscreenComponent (which Activity uses internally).

Solution

Added a check for OffscreenComponent visibility transitions behind a new feature flag enableEventAPIActivityFix.


Files Modified

1. packages/react-reconciler/src/ReactFiberCommitWork.js

Added blur event handling for Activity (OffscreenComponent):

// Check if an OffscreenComponent (Activity) is being hidden with focus inside.
if (enableEventAPIActivityFix) {
  if (
    finishedWork.tag === OffscreenComponent &&
    current !== null &&
    current.memoizedState === null && // was visible
    finishedWork.memoizedState !== null && // now hidden
    doesFiberContain(finishedWork, focusedInstanceHandle)
  ) {
    shouldFireAfterActiveInstanceBlur = true;
    beforeActiveInstanceBlur(finishedWork);
  }
}

2. Feature Flag Files

Added enableEventAPIActivityFix to all feature flag files:

File Value
ReactFeatureFlags.js false (default)
ReactFeatureFlags.www-dynamic.js __VARIANT__ (dynamic)
ReactFeatureFlags.www.js imports from dynamic
ReactFeatureFlags.test-renderer.js false
ReactFeatureFlags.test-renderer.www.js false
ReactFeatureFlags.test-renderer.native-fb.js false

3. packages/react-dom/src/events/__tests__/DOMPluginEventSystem-test.internal.js

Added 22 new tests (from 120 → 142 total):

Activity tests (6 tests)

  • beforeblur and afterblur are called after a focused element is hidden
  • beforeblur and afterblur are called after a nested focused element is hidden
  • beforeblur should skip handlers from a hidden subtree after the focused element is hidden via Activity

LegacyHidden tests (6 tests)

  • beforeblur and afterblur are not called after a focused element is hidden inside LegacyHidden
  • beforeblur and afterblur are not called after a nested focused element is hidden inside LegacyHidden
  • beforeblur is not called after the focused element is hidden inside LegacyHidden

Nested Activity + LegacyHidden tests (10 tests)

Nesting Scenario Expected
<Activity><LegacyHidden> Hide outer (Activity) blur fires
<Activity><LegacyHidden> Hide inner (LegacyHidden) no blur
<Activity><LegacyHidden> Hide both simultaneously blur fires once
<Activity><LegacyHidden> Hide LegacyHidden, then Activity blur on Activity
<Activity><LegacyHidden> Hide Activity, then LegacyHidden blur on Activity, no second blur
<LegacyHidden><Activity> Hide outer (LegacyHidden) no blur
<LegacyHidden><Activity> Hide inner (Activity) blur fires
<LegacyHidden><Activity> Hide both simultaneously blur fires once
<LegacyHidden><Activity> Hide LegacyHidden, then Activity blur on Activity
<LegacyHidden><Activity> Hide Activity, then LegacyHidden blur on Activity, no second blur

Key Design Decisions

  1. Activity triggers blur, LegacyHidden does not - LegacyHidden behavior remains unchanged for backwards compatibility

  2. Inline gate pattern - Tests use if(gate('enableEventAPIActivityFix')) to test both flag states in a single test

  3. Works with enableViewTransition - The fix correctly handles the ViewTransition early exit path


Test Results

  • ✅ 142 tests pass with variant=true (enableEventAPIActivityFix ON)
  • ✅ 142 tests pass with variant=false (enableEventAPIActivityFix OFF)
  • ✅ All tests work correctly with enableViewTransition=true

@meta-cla meta-cla Bot added the CLA Signed label Jan 19, 2026
@github-actions github-actions Bot added the React Core Team Opened by a member of the React Core Team label Jan 19, 2026
@react-sizebot

react-sizebot commented Jan 19, 2026

Copy link
Copy Markdown

Comparing: 3aaab92...c2cc16c

Critical size changes

Includes critical production bundles, as well as any change greater than 2%:

Name +/- Base Current +/- gzip Base gzip Current gzip
oss-stable/react-dom/cjs/react-dom.production.js = 6.84 kB 6.84 kB +0.05% 1.88 kB 1.88 kB
oss-stable/react-dom/cjs/react-dom-client.production.js = 610.53 kB 610.53 kB = 107.91 kB 107.91 kB
oss-experimental/react-dom/cjs/react-dom.production.js = 6.84 kB 6.84 kB +0.05% 1.88 kB 1.88 kB
oss-experimental/react-dom/cjs/react-dom-client.production.js = 676.46 kB 676.46 kB = 118.87 kB 118.87 kB
facebook-www/ReactDOM-prod.classic.js +0.05% 696.77 kB 697.10 kB +0.04% 122.49 kB 122.53 kB
facebook-www/ReactDOM-prod.modern.js +0.05% 687.15 kB 687.48 kB +0.04% 120.89 kB 120.93 kB

Significant size changes

Includes any change greater than 0.2%:

(No significant changes)

Generated by 🚫 dangerJS against c2cc16c

@rickhanlonii

Copy link
Copy Markdown
Contributor Author

@Ranamalsingh12 future AI generated comment spam will result in a ban.

shouldFireAfterActiveInstanceBlur = true;
beforeActiveInstanceBlur(finishedWork);
}
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Actually, this might double blur since Suspense uses Activity - let me look into it more

Eliezermga

This comment was marked as off-topic.

@rickhanlonii
rickhanlonii force-pushed the rh/ff-fix-event-api-activity branch from b74caa8 to c2cc16c Compare February 5, 2026 03:48
});

if (withMemo) {
// BUG: when suspended children are not updated blur is not fired.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This test shows an existing bug in Suspense where we don't fire blur if the children are not updated while suspending (the memo() case).

'beforeblur - parent',
'beforeblur - child',
'afterblur - parent',
]);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I actually don't know why this works in the memo() case. It should be the same as Supense - the children shouldn't be visited because of the memo so no event fired?


expect(log).toEqual([
// BUG: we already fired blur events for the hidden element.
// This is a bug that's always existed.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Another existing Suspense bug - blur fired twice if the tree suspends while already suspended.


if (withMemo && gate('enableEventAPIActivityFix')) {
// Bug in new flag: if the `withMemo` case above is fixed (so blur always fires),
// this would be wrong. But as long as both bugs exist, this seems right.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This shit is crazy broken

if (gate('enableEventAPIActivityFix')) {
expect(log).toEqual([
// BUG: unlike the last test, these cause double events if the children are not memoized.
// Seems pretty bad, idk.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This bug is a blocker for this flag. I'm amazed this thing is even in prod.

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

Labels

CLA Signed React Core Team Opened by a member of the React Core Team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants