@samitouri / QOS-React-1 / commits / 9c0323e2cf

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 }