Skip to content

Commit ab84793

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 7a2609e commit ab84793

13 files changed

+13
-263
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,
@@ -2330,90 +2329,11 @@ function createEffectInstance(): EffectInstance {
23302329
return {destroy: undefined};
23312330
}
23322331

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

24192339
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
@@ -195,8 +195,6 @@ export const enableRenderableContext = true;
195195
// when we plan to enable them.
196196
// -----------------------------------------------------------------------------
197197

198-
export const enableUseRefAccessWarning = false;
199-
200198
// Enables time slicing for updates that aren't wrapped in startTransition.
201199
export const forceConcurrentByDefaultForTesting = false;
202200

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

-1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,5 @@ export const enableDeferRootSchedulingToMicrotask = __VARIANT__;
2525
export const enableInfiniteRenderLoopDetection = __VARIANT__;
2626
export const enableRenderableContext = __VARIANT__;
2727
export const enableUnifiedSyncLane = __VARIANT__;
28-
export const enableUseRefAccessWarning = __VARIANT__;
2928
export const passChildrenWhenCloningPersistedNodes = __VARIANT__;
3029
export const useModernStrictMode = __VARIANT__;

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

-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ export const {
2727
enableInfiniteRenderLoopDetection,
2828
enableRenderableContext,
2929
enableUnifiedSyncLane,
30-
enableUseRefAccessWarning,
3130
passChildrenWhenCloningPersistedNodes,
3231
useModernStrictMode,
3332
} = dynamicFlags;

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

-1
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,6 @@ export const enableRetryLaneExpiration = false;
9191
export const retryLaneExpirationMs = 5000;
9292
export const syncLaneExpirationMs = 250;
9393
export const transitionLaneExpirationMs = 5000;
94-
export const enableUseRefAccessWarning = false;
9594
export const disableSchedulerTimeoutInWorkLoop = false;
9695
export const enableLazyContextPropagation = false;
9796
export const enableLegacyHidden = false;

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

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

51-
export const enableUseRefAccessWarning = false;
52-
5351
export const disableSchedulerTimeoutInWorkLoop = false;
5452
export const enableLazyContextPropagation = false;
5553
export const enableLegacyHidden = false;

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

-1
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,6 @@ export const enableCPUSuspense = true;
4343
export const enableUseMemoCacheHook = true;
4444
export const enableUseEffectEventHook = false;
4545
export const favorSafetyOverHydrationPerf = true;
46-
export const enableUseRefAccessWarning = false;
4746
export const enableInfiniteRenderLoopDetection = false;
4847
export const enableRenderableContext = false;
4948

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

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

53-
export const enableUseRefAccessWarning = false;
54-
5553
export const disableSchedulerTimeoutInWorkLoop = false;
5654
export const enableLazyContextPropagation = false;
5755
export const enableLegacyHidden = false;

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

-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
// with the __VARIANT__ set to `true`, and once set to `false`.
1515

1616
export const disableIEWorkarounds = __VARIANT__;
17-
export const enableUseRefAccessWarning = __VARIANT__;
1817
export const disableSchedulerTimeoutInWorkLoop = __VARIANT__;
1918
export const enableLazyContextPropagation = __VARIANT__;
2019
export const forceConcurrentByDefaultForTesting = __VARIANT__;

packages/shared/forks/ReactFeatureFlags.www.js

-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ export const {
1818
disableIEWorkarounds,
1919
enableTrustedTypesIntegration,
2020
enableDebugTracing,
21-
enableUseRefAccessWarning,
2221
enableLazyContextPropagation,
2322
enableUnifiedSyncLane,
2423
enableRetryLaneExpiration,

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)