Skip to content

Commit ceeb1b4

Browse files
committed
Cleanup enableUseRefAccessWarning flag
I don't think this flag has a path forward in the current implementation. The detection by stack trace is too brittle to detect the lazy initialization pattern reliably (see e.g. some internal tests that expect the warning because they use lazy intialization, but a slightly different pattern then the expected pattern. I think a new version of this could be to fully ban ref access during render with an alternative API for the exceptional cases that today require ref access during render.
1 parent 93f9179 commit ceeb1b4

13 files changed

+15
-265
lines changed

packages/react-reconciler/src/ReactFiberHooks.js

+3-83
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@ import {
3333
enableDebugTracing,
3434
enableSchedulingProfiler,
3535
enableCache,
36-
enableUseRefAccessWarning,
3736
enableLazyContextPropagation,
3837
enableTransitionTracing,
3938
enableUseMemoCacheHook,
@@ -2329,90 +2328,11 @@ function createEffectInstance(): EffectInstance {
23292328
return {destroy: undefined};
23302329
}
23312330

2332-
let stackContainsErrorMessage: boolean | null = null;
2333-
2334-
function getCallerStackFrame(): string {
2335-
// eslint-disable-next-line react-internal/prod-error-codes
2336-
const stackFrames = new Error('Error message').stack.split('\n');
2337-
2338-
// Some browsers (e.g. Chrome) include the error message in the stack
2339-
// but others (e.g. Firefox) do not.
2340-
if (stackContainsErrorMessage === null) {
2341-
stackContainsErrorMessage = stackFrames[0].includes('Error message');
2342-
}
2343-
2344-
return stackContainsErrorMessage
2345-
? stackFrames.slice(3, 4).join('\n')
2346-
: stackFrames.slice(2, 3).join('\n');
2347-
}
2348-
23492331
function mountRef<T>(initialValue: T): {current: T} {
23502332
const hook = mountWorkInProgressHook();
2351-
if (enableUseRefAccessWarning) {
2352-
if (__DEV__) {
2353-
// Support lazy initialization pattern shown in docs.
2354-
// We need to store the caller stack frame so that we don't warn on subsequent renders.
2355-
let hasBeenInitialized = initialValue != null;
2356-
let lazyInitGetterStack = null;
2357-
let didCheckForLazyInit = false;
2358-
2359-
// Only warn once per component+hook.
2360-
let didWarnAboutRead = false;
2361-
let didWarnAboutWrite = false;
2362-
2363-
let current = initialValue;
2364-
const ref = {
2365-
get current() {
2366-
if (!hasBeenInitialized) {
2367-
didCheckForLazyInit = true;
2368-
lazyInitGetterStack = getCallerStackFrame();
2369-
} else if (currentlyRenderingFiber !== null && !didWarnAboutRead) {
2370-
if (
2371-
lazyInitGetterStack === null ||
2372-
lazyInitGetterStack !== getCallerStackFrame()
2373-
) {
2374-
didWarnAboutRead = true;
2375-
console.warn(
2376-
'%s: Unsafe read of a mutable value during render.\n\n' +
2377-
'Reading from a ref during render is only safe if:\n' +
2378-
'1. The ref value has not been updated, or\n' +
2379-
'2. The ref holds a lazily-initialized value that is only set once.\n',
2380-
getComponentNameFromFiber(currentlyRenderingFiber) || 'Unknown',
2381-
);
2382-
}
2383-
}
2384-
return current;
2385-
},
2386-
set current(value: any) {
2387-
if (currentlyRenderingFiber !== null && !didWarnAboutWrite) {
2388-
if (hasBeenInitialized || !didCheckForLazyInit) {
2389-
didWarnAboutWrite = true;
2390-
console.warn(
2391-
'%s: Unsafe write of a mutable value during render.\n\n' +
2392-
'Writing to a ref during render is only safe if the ref holds ' +
2393-
'a lazily-initialized value that is only set once.\n',
2394-
getComponentNameFromFiber(currentlyRenderingFiber) || 'Unknown',
2395-
);
2396-
}
2397-
}
2398-
2399-
hasBeenInitialized = true;
2400-
current = value;
2401-
},
2402-
};
2403-
Object.seal(ref);
2404-
hook.memoizedState = ref;
2405-
return ref;
2406-
} else {
2407-
const ref = {current: initialValue};
2408-
hook.memoizedState = ref;
2409-
return ref;
2410-
}
2411-
} else {
2412-
const ref = {current: initialValue};
2413-
hook.memoizedState = ref;
2414-
return ref;
2415-
}
2333+
const ref = {current: initialValue};
2334+
hook.memoizedState = ref;
2335+
return ref;
24162336
}
24172337

24182338
function updateRef<T>(initialValue: T): {current: T} {

packages/react-reconciler/src/__tests__/useRef-test.internal.js

-125
Original file line numberDiff line numberDiff line change
@@ -160,24 +160,6 @@ describe('useRef', () => {
160160
});
161161
});
162162

163-
// @gate enableUseRefAccessWarning
164-
it('should warn about reads during render', async () => {
165-
function Example() {
166-
const ref = useRef(123);
167-
let value;
168-
expect(() => {
169-
value = ref.current;
170-
}).toWarnDev([
171-
'Example: Unsafe read of a mutable value during render.',
172-
]);
173-
return value;
174-
}
175-
176-
await act(() => {
177-
ReactNoop.render(<Example />);
178-
});
179-
});
180-
181163
it('should not warn about lazy init during render', async () => {
182164
function Example() {
183165
const ref1 = useRef(null);
@@ -221,113 +203,6 @@ describe('useRef', () => {
221203
});
222204
});
223205

224-
// @gate enableUseRefAccessWarning
225-
it('should warn about unconditional lazy init during render', async () => {
226-
function Example() {
227-
const ref1 = useRef(null);
228-
const ref2 = useRef(undefined);
229-
230-
if (shouldExpectWarning) {
231-
expect(() => {
232-
ref1.current = 123;
233-
}).toWarnDev([
234-
'Example: Unsafe write of a mutable value during render',
235-
]);
236-
expect(() => {
237-
ref2.current = 123;
238-
}).toWarnDev([
239-
'Example: Unsafe write of a mutable value during render',
240-
]);
241-
} else {
242-
ref1.current = 123;
243-
ref1.current = 123;
244-
}
245-
246-
// But only warn once
247-
ref1.current = 345;
248-
ref1.current = 345;
249-
250-
return null;
251-
}
252-
253-
let shouldExpectWarning = true;
254-
await act(() => {
255-
ReactNoop.render(<Example />);
256-
});
257-
258-
// Should not warn again on update.
259-
shouldExpectWarning = false;
260-
await act(() => {
261-
ReactNoop.render(<Example />);
262-
});
263-
});
264-
265-
// @gate enableUseRefAccessWarning
266-
it('should warn about reads to ref after lazy init pattern', async () => {
267-
function Example() {
268-
const ref1 = useRef(null);
269-
const ref2 = useRef(undefined);
270-
271-
// Read 1: safe because lazy init:
272-
if (ref1.current === null) {
273-
ref1.current = 123;
274-
}
275-
if (ref2.current === undefined) {
276-
ref2.current = 123;
277-
}
278-
279-
let value;
280-
expect(() => {
281-
value = ref1.current;
282-
}).toWarnDev(['Example: Unsafe read of a mutable value during render']);
283-
expect(() => {
284-
value = ref2.current;
285-
}).toWarnDev(['Example: Unsafe read of a mutable value during render']);
286-
287-
// But it should only warn once.
288-
value = ref1.current;
289-
value = ref2.current;
290-
291-
return value;
292-
}
293-
294-
await act(() => {
295-
ReactNoop.render(<Example />);
296-
});
297-
});
298-
299-
// @gate enableUseRefAccessWarning
300-
it('should warn about writes to ref after lazy init pattern', async () => {
301-
function Example() {
302-
const ref1 = useRef(null);
303-
const ref2 = useRef(undefined);
304-
// Read: safe because lazy init:
305-
if (ref1.current === null) {
306-
ref1.current = 123;
307-
}
308-
if (ref2.current === undefined) {
309-
ref2.current = 123;
310-
}
311-
312-
expect(() => {
313-
ref1.current = 456;
314-
}).toWarnDev([
315-
'Example: Unsafe write of a mutable value during render',
316-
]);
317-
expect(() => {
318-
ref2.current = 456;
319-
}).toWarnDev([
320-
'Example: Unsafe write of a mutable value during render',
321-
]);
322-
323-
return null;
324-
}
325-
326-
await act(() => {
327-
ReactNoop.render(<Example />);
328-
});
329-
});
330-
331206
it('should not warn about reads or writes within effect', async () => {
332207
function Example() {
333208
const ref = useRef(123);

packages/shared/ReactFeatureFlags.js

-2
Original file line numberDiff line numberDiff line change
@@ -193,8 +193,6 @@ export const enableRenderableContext = __NEXT_MAJOR__;
193193
// when we plan to enable them.
194194
// -----------------------------------------------------------------------------
195195

196-
export const enableUseRefAccessWarning = false;
197-
198196
// Enables time slicing for updates that aren't wrapped in startTransition.
199197
export const forceConcurrentByDefaultForTesting = false;
200198

packages/shared/forks/ReactFeatureFlags.native-fb-dynamic.js

-1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,5 @@ export const enableDeferRootSchedulingToMicrotask = __VARIANT__;
2626
export const enableInfiniteRenderLoopDetection = __VARIANT__;
2727
export const enableRenderableContext = __VARIANT__;
2828
export const enableUnifiedSyncLane = __VARIANT__;
29-
export const enableUseRefAccessWarning = __VARIANT__;
3029
export const passChildrenWhenCloningPersistedNodes = __VARIANT__;
3130
export const useModernStrictMode = __VARIANT__;

packages/shared/forks/ReactFeatureFlags.native-fb.js

-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ export const {
2828
enableInfiniteRenderLoopDetection,
2929
enableRenderableContext,
3030
enableUnifiedSyncLane,
31-
enableUseRefAccessWarning,
3231
passChildrenWhenCloningPersistedNodes,
3332
useModernStrictMode,
3433
} = dynamicFlags;

packages/shared/forks/ReactFeatureFlags.native-oss.js

-1
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,6 @@ export const enableRetryLaneExpiration = false;
9393
export const retryLaneExpirationMs = 5000;
9494
export const syncLaneExpirationMs = 250;
9595
export const transitionLaneExpirationMs = 5000;
96-
export const enableUseRefAccessWarning = false;
9796
export const disableSchedulerTimeoutInWorkLoop = false;
9897
export const enableLazyContextPropagation = false;
9998
export const enableLegacyHidden = false;

packages/shared/forks/ReactFeatureFlags.test-renderer.js

-2
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,6 @@ export const retryLaneExpirationMs = 5000;
4949
export const syncLaneExpirationMs = 250;
5050
export const transitionLaneExpirationMs = 5000;
5151

52-
export const enableUseRefAccessWarning = false;
53-
5452
export const disableSchedulerTimeoutInWorkLoop = false;
5553
export const enableLazyContextPropagation = false;
5654
export const enableLegacyHidden = false;

packages/shared/forks/ReactFeatureFlags.test-renderer.native-fb.js

-1
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@ export const enableCPUSuspense = true;
4444
export const enableUseMemoCacheHook = true;
4545
export const enableUseEffectEventHook = false;
4646
export const favorSafetyOverHydrationPerf = true;
47-
export const enableUseRefAccessWarning = false;
4847
export const enableInfiniteRenderLoopDetection = false;
4948
export const enableRenderableContext = false;
5049

packages/shared/forks/ReactFeatureFlags.test-renderer.www.js

-2
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,6 @@ export const retryLaneExpirationMs = 5000;
5151
export const syncLaneExpirationMs = 250;
5252
export const transitionLaneExpirationMs = 5000;
5353

54-
export const enableUseRefAccessWarning = false;
55-
5654
export const disableSchedulerTimeoutInWorkLoop = false;
5755
export const enableLazyContextPropagation = false;
5856
export const enableLegacyHidden = false;

packages/shared/forks/ReactFeatureFlags.www-dynamic.js

-2
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,6 @@
1616
export const disableInputAttributeSyncing = __VARIANT__;
1717
export const disableIEWorkarounds = __VARIANT__;
1818
export const enableBigIntSupport = __VARIANT__;
19-
export const enableLegacyFBSupport = __VARIANT__;
20-
export const enableUseRefAccessWarning = __VARIANT__;
2119
export const disableSchedulerTimeoutInWorkLoop = __VARIANT__;
2220
export const enableLazyContextPropagation = __VARIANT__;
2321
export const forceConcurrentByDefaultForTesting = __VARIANT__;

packages/shared/forks/ReactFeatureFlags.www.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,7 @@ export const {
1919
disableIEWorkarounds,
2020
enableBigIntSupport,
2121
enableTrustedTypesIntegration,
22-
enableLegacyFBSupport,
2322
enableDebugTracing,
24-
enableUseRefAccessWarning,
2523
enableLazyContextPropagation,
2624
enableUnifiedSyncLane,
2725
enableRetryLaneExpiration,
@@ -118,5 +116,7 @@ export const disableLegacyMode = false;
118116

119117
export const disableDOMTestUtils = false;
120118

119+
export const enableLegacyFBSupport = true;
120+
121121
// Flow magic to verify the exports of this file match the original version.
122122
((((null: any): ExportsType): FeatureFlagsType): ExportsType);

packages/use-sync-external-store/src/__tests__/useSyncExternalStoreNative-test.js

-1
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,6 @@ describe('useSyncExternalStore (userspace shim, server rendering)', () => {
124124
expect(root).toMatchRenderedOutput('client');
125125
});
126126

127-
// @gate !(enableUseRefAccessWarning && __DEV__)
128127
test('Using isEqual to bailout', async () => {
129128
const store = createExternalStore({a: 0, b: 0});
130129

0 commit comments

Comments
 (0)