Stabilize reactFragments host node handle (#35642)
https://github.com/facebook/react/pull/34935 Introduced `unstable_reactFragments` handle on DOM nodes to enable caching of Observers. This has been tested in production and is stable so it can be rolled out with the Fragment Refs feature.
Jack Pope committed
Mar 3, 2026 at 15:44 UTC
9c0323e2cf9be543d6eaa44419598af56922603f
3 files changed
+21
-29
packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js
+6
-6
@@ -218,7 +218,7 @@ export type Instance = Element;
218
export type TextInstance = Text;
219
220
type InstanceWithFragmentHandles = Instance & {
221
- unstable_reactFragments?: Set<FragmentInstanceType>,
221
+ reactFragments?: Set<FragmentInstanceType>,
222
};
223
224
declare class ActivityInterface extends Comment {}
@@ -3578,10 +3578,10 @@ function addFragmentHandleToInstance(
3578
fragmentInstance: FragmentInstanceType,
3579
): void {
3580
if (enableFragmentRefsInstanceHandles) {
3581
- if (instance.unstable_reactFragments == null) {
3582
- instance.unstable_reactFragments = new Set();
3581
+ if (instance.reactFragments == null) {
3582
+ instance.reactFragments = new Set();
3583
}
3584
- instance.unstable_reactFragments.add(fragmentInstance);
3584
+ instance.reactFragments.add(fragmentInstance);
3585
}
3586
}
3587
@@ -3647,8 +3647,8 @@ export function deleteChildFromFragmentInstance(
3647
}
3648
}
3649
if (enableFragmentRefsInstanceHandles) {
3650
- if (instance.unstable_reactFragments != null) {
3651
- instance.unstable_reactFragments.delete(fragmentInstance);
3650
+ if (instance.reactFragments != null) {
3651
+ instance.reactFragments.delete(fragmentInstance);
3652
}
3653
}
3654
}
packages/react-dom/src/__tests__/ReactDOMFragmentRefs-test.js
+9
-17
@@ -137,26 +137,18 @@ describe('FragmentRefs', () => {
137
const childB = document.querySelector('#childB');
138
const childC = document.querySelector('#childC');
139
140
- expect(childA.unstable_reactFragments.has(fragmentRef.current)).toBe(true);
141
- expect(childB.unstable_reactFragments.has(fragmentRef.current)).toBe(true);
142
- expect(childC.unstable_reactFragments.has(fragmentRef.current)).toBe(false);
143
- expect(childA.unstable_reactFragments.has(fragmentParentRef.current)).toBe(
144
- true,
145
- );
146
- expect(childB.unstable_reactFragments.has(fragmentParentRef.current)).toBe(
147
- true,
148
- );
149
- expect(childC.unstable_reactFragments.has(fragmentParentRef.current)).toBe(
150
- true,
151
- );
140
+ expect(childA.reactFragments.has(fragmentRef.current)).toBe(true);
141
+ expect(childB.reactFragments.has(fragmentRef.current)).toBe(true);
142
+ expect(childC.reactFragments.has(fragmentRef.current)).toBe(false);
143
+ expect(childA.reactFragments.has(fragmentParentRef.current)).toBe(true);
144
+ expect(childB.reactFragments.has(fragmentParentRef.current)).toBe(true);
145
+ expect(childC.reactFragments.has(fragmentParentRef.current)).toBe(true);
146
147
await act(() => root.render(<Test show={true} />));
148
149
const childD = document.querySelector('#childD');
156
- expect(childD.unstable_reactFragments.has(fragmentRef.current)).toBe(false);
157
- expect(childD.unstable_reactFragments.has(fragmentParentRef.current)).toBe(
158
- true,
159
- );
150
+ expect(childD.reactFragments.has(fragmentRef.current)).toBe(false);
151
+ expect(childD.reactFragments.has(fragmentParentRef.current)).toBe(true);
152
});
153
154
describe('focus methods', () => {
@@ -1104,7 +1096,7 @@ describe('FragmentRefs', () => {
1096
}
1097
const observer = new IntersectionObserver(entries => {
1098
entries.forEach(entry => {
1107
- const fragmentInstances = entry.target.unstable_reactFragments;
1099
+ const fragmentInstances = entry.target.reactFragments;
1100
if (fragmentInstances) {
1101
Array.from(fragmentInstances).forEach(fInstance => {
1102
const cbs = targetToCallbackMap.get(fInstance) || [];
packages/react-native-renderer/src/ReactFiberConfigFabric.js
+6
-6
@@ -124,7 +124,7 @@ export type TextInstance = {
124
export type HydratableInstance = Instance | TextInstance;
125
export type PublicInstance = ReactNativePublicInstance;
126
type PublicInstanceWithFragmentHandles = PublicInstance & {
127
- unstable_reactFragments?: Set<FragmentInstanceType>,
127
+ reactFragments?: Set<FragmentInstanceType>,
128
};
129
export type Container = {
130
containerTag: number,
@@ -856,10 +856,10 @@ function addFragmentHandleToInstance(
856
fragmentInstance: FragmentInstanceType,
857
): void {
858
if (enableFragmentRefsInstanceHandles) {
859
- if (instance.unstable_reactFragments == null) {
860
- instance.unstable_reactFragments = new Set();
859
+ if (instance.reactFragments == null) {
860
+ instance.reactFragments = new Set();
861
}
862
- instance.unstable_reactFragments.add(fragmentInstance);
862
+ instance.reactFragments.add(fragmentInstance);
863
}
864
}
865
@@ -924,8 +924,8 @@ export function deleteChildFromFragmentInstance(
924
instance,
925
): any): PublicInstanceWithFragmentHandles);
926
if (enableFragmentRefsInstanceHandles) {
927
- if (publicInstance.unstable_reactFragments != null) {
928
- publicInstance.unstable_reactFragments.delete(fragmentInstance);
927
+ if (publicInstance.reactFragments != null) {
928
+ publicInstance.reactFragments.delete(fragmentInstance);
929
}
930
}
931
}