Remove enableRefAsProp feature flag (#30346)

The flag is fully rolled out.
This commit is contained in:
Jan Kassens 2024-11-04 14:30:58 -05:00 committed by GitHub
parent 543eb09321
commit 07aa494432
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
29 changed files with 113 additions and 859 deletions

View File

@ -6,7 +6,7 @@
*/ */
import {REACT_ELEMENT_TYPE, REACT_FRAGMENT_TYPE} from 'shared/ReactSymbols'; import {REACT_ELEMENT_TYPE, REACT_FRAGMENT_TYPE} from 'shared/ReactSymbols';
import {disableStringRefs, enableRefAsProp} from 'shared/ReactFeatureFlags'; import {disableStringRefs} from 'shared/ReactFeatureFlags';
const {assertConsoleLogsCleared} = require('internal-test-utils/consoleMock'); const {assertConsoleLogsCleared} = require('internal-test-utils/consoleMock');
import isArray from 'shared/isArray'; import isArray from 'shared/isArray';
@ -42,7 +42,7 @@ function assertYieldsWereCleared(root) {
} }
function createJSXElementForTestComparison(type, props) { function createJSXElementForTestComparison(type, props) {
if (__DEV__ && enableRefAsProp) { if (__DEV__) {
const element = { const element = {
$$typeof: REACT_ELEMENT_TYPE, $$typeof: REACT_ELEMENT_TYPE,
type: type, type: type,

View File

@ -44,7 +44,6 @@ import {
disableStringRefs, disableStringRefs,
enableBinaryFlight, enableBinaryFlight,
enablePostpone, enablePostpone,
enableRefAsProp,
enableFlightReadableStream, enableFlightReadableStream,
enableOwnerStacks, enableOwnerStacks,
enableServerComponentLogs, enableServerComponentLogs,
@ -676,7 +675,7 @@ function createElement(
| React$Element<any> | React$Element<any>
| LazyComponent<React$Element<any>, SomeChunk<React$Element<any>>> { | LazyComponent<React$Element<any>, SomeChunk<React$Element<any>>> {
let element: any; let element: any;
if (__DEV__ && enableRefAsProp) { if (__DEV__) {
// `ref` is non-enumerable in dev // `ref` is non-enumerable in dev
element = ({ element = ({
$$typeof: REACT_ELEMENT_TYPE, $$typeof: REACT_ELEMENT_TYPE,

View File

@ -868,89 +868,6 @@ describe('Store (legacy)', () => {
`); `);
}); });
// TODO: These tests don't work when enableRefAsProp is on because the
// JSX runtime that's injected into the test environment by the compiler
// is not compatible with older versions of React. Need to configure the
// the test environment in such a way that certain test modules like this
// one can use an older transform.
if (!require('shared/ReactFeatureFlags').enableRefAsProp) {
it('should support expanding deep parts of the tree', () => {
const Wrapper = ({forwardedRef}) =>
React.createElement(Nested, {
depth: 3,
forwardedRef: forwardedRef,
});
const Nested = ({depth, forwardedRef}) =>
depth > 0
? React.createElement(Nested, {
depth: depth - 1,
forwardedRef: forwardedRef,
})
: React.createElement('div', {
ref: forwardedRef,
});
let ref = null;
const refSetter = value => {
ref = value;
};
act(() =>
ReactDOM.render(
React.createElement(Wrapper, {
forwardedRef: refSetter,
}),
document.createElement('div'),
),
);
expect(store).toMatchInlineSnapshot(`
[root]
<Wrapper>
`);
const deepestedNodeID = global.agent.getIDForHostInstance(ref);
act(() => store.toggleIsCollapsed(deepestedNodeID, false));
expect(store).toMatchInlineSnapshot(`
[root]
<Wrapper>
<Nested>
<Nested>
<Nested>
<Nested>
<div>
`);
const rootID = store.getElementIDAtIndex(0);
act(() => store.toggleIsCollapsed(rootID, true));
expect(store).toMatchInlineSnapshot(`
[root]
<Wrapper>
`);
act(() => store.toggleIsCollapsed(rootID, false));
expect(store).toMatchInlineSnapshot(`
[root]
<Wrapper>
<Nested>
<Nested>
<Nested>
<Nested>
<div>
`);
const id = store.getElementIDAtIndex(1);
act(() => store.toggleIsCollapsed(id, true));
expect(store).toMatchInlineSnapshot(`
[root]
<Wrapper>
<Nested>
`);
act(() => store.toggleIsCollapsed(id, false));
expect(store).toMatchInlineSnapshot(`
[root]
<Wrapper>
<Nested>
<Nested>
<Nested>
<Nested>
<div>
`);
});
}
it('should support reordering of children', () => { it('should support reordering of children', () => {
const Root = ({children}) => React.createElement('div', null, children); const Root = ({children}) => React.createElement('div', null, children);
const Component = () => React.createElement('div', null); const Component = () => React.createElement('div', null);

View File

@ -197,223 +197,6 @@ describe('ReactFunctionComponent', () => {
.rejects.toThrowError(); .rejects.toThrowError();
}); });
// @gate !enableRefAsProp || !__DEV__
it('should warn when given a string ref', async () => {
function Indirection(props) {
return <div>{props.children}</div>;
}
class ParentUsingStringRef extends React.Component {
render() {
return (
<Indirection>
<FunctionComponent name="A" ref="stateless" />
</Indirection>
);
}
}
await expect(async () => {
const container = document.createElement('div');
const root = ReactDOMClient.createRoot(container);
await act(() => {
root.render(<ParentUsingStringRef />);
});
}).toErrorDev(
'Function components cannot be given refs. ' +
'Attempts to access this ref will fail. ' +
'Did you mean to use React.forwardRef()?\n\n' +
'Check the render method ' +
'of `ParentUsingStringRef`.\n' +
' in FunctionComponent (at **)\n' +
' in div (at **)\n' +
' in Indirection (at **)\n' +
' in ParentUsingStringRef (at **)',
);
// No additional warnings should be logged
const container = document.createElement('div');
const root = ReactDOMClient.createRoot(container);
await act(() => {
root.render(<ParentUsingStringRef />);
});
});
// @gate !enableRefAsProp || !__DEV__
it('should warn when given a function ref', async () => {
function Indirection(props) {
return <div>{props.children}</div>;
}
const ref = jest.fn();
class ParentUsingFunctionRef extends React.Component {
render() {
return (
<Indirection>
<FunctionComponent name="A" ref={ref} />
</Indirection>
);
}
}
await expect(async () => {
const container = document.createElement('div');
const root = ReactDOMClient.createRoot(container);
await act(() => {
root.render(<ParentUsingFunctionRef />);
});
}).toErrorDev(
'Function components cannot be given refs. ' +
'Attempts to access this ref will fail. ' +
'Did you mean to use React.forwardRef()?\n\n' +
'Check the render method ' +
'of `ParentUsingFunctionRef`.\n' +
' in FunctionComponent (at **)\n' +
' in div (at **)\n' +
' in Indirection (at **)\n' +
' in ParentUsingFunctionRef (at **)',
);
expect(ref).not.toHaveBeenCalled();
// No additional warnings should be logged
const container = document.createElement('div');
const root = ReactDOMClient.createRoot(container);
await act(() => {
root.render(<ParentUsingFunctionRef />);
});
});
// @gate !enableRefAsProp || !__DEV__
it('deduplicates ref warnings based on element or owner', async () => {
// When owner uses JSX, we can use exact line location to dedupe warnings
class AnonymousParentUsingJSX extends React.Component {
render() {
return <FunctionComponent name="A" ref={() => {}} />;
}
}
let instance1;
await expect(async () => {
const container = document.createElement('div');
const root = ReactDOMClient.createRoot(container);
await act(() => {
root.render(
<AnonymousParentUsingJSX ref={current => (instance1 = current)} />,
);
});
}).toErrorDev('Function components cannot be given refs.');
// Should be deduped (offending element is on the same line):
instance1.forceUpdate();
// Should also be deduped (offending element is on the same line):
let container = document.createElement('div');
let root = ReactDOMClient.createRoot(container);
await act(() => {
root.render(<AnonymousParentUsingJSX />);
});
// When owner doesn't use JSX, and is anonymous, we warn once per internal instance.
class AnonymousParentNotUsingJSX extends React.Component {
render() {
return React.createElement(FunctionComponent, {
name: 'A',
ref: () => {},
});
}
}
let instance2;
await expect(async () => {
container = document.createElement('div');
root = ReactDOMClient.createRoot(container);
await act(() => {
root.render(
<AnonymousParentNotUsingJSX ref={current => (instance2 = current)} />,
);
});
}).toErrorDev('Function components cannot be given refs.');
// Should be deduped (same internal instance, no additional warnings)
instance2.forceUpdate();
// Could not be differentiated (since owner is anonymous and no source location)
container = document.createElement('div');
root = ReactDOMClient.createRoot(container);
await act(() => {
root.render(<AnonymousParentNotUsingJSX />);
});
// When owner doesn't use JSX, but is named, we warn once per owner name
class NamedParentNotUsingJSX extends React.Component {
render() {
return React.createElement(FunctionComponent, {
name: 'A',
ref: () => {},
});
}
}
let instance3;
await expect(async () => {
container = document.createElement('div');
root = ReactDOMClient.createRoot(container);
await act(() => {
root.render(
<NamedParentNotUsingJSX ref={current => (instance3 = current)} />,
);
});
}).toErrorDev('Function components cannot be given refs.');
// Should be deduped (same owner name, no additional warnings):
instance3.forceUpdate();
// Should also be deduped (same owner name, no additional warnings):
container = document.createElement('div');
root = ReactDOMClient.createRoot(container);
await act(() => {
root.render(<NamedParentNotUsingJSX />);
});
});
// This guards against a regression caused by clearing the current debug fiber.
// https://github.com/facebook/react/issues/10831
// @gate !disableLegacyContext || !__DEV__
// @gate !enableRefAsProp || !__DEV__
it('should warn when giving a function ref with context', async () => {
function Child() {
return null;
}
Child.contextTypes = {
foo: PropTypes.string,
};
class Parent extends React.Component {
static childContextTypes = {
foo: PropTypes.string,
};
getChildContext() {
return {
foo: 'bar',
};
}
render() {
return <Child ref={function () {}} />;
}
}
await expect(async () => {
const container = document.createElement('div');
const root = ReactDOMClient.createRoot(container);
await act(() => {
root.render(<Parent />);
});
}).toErrorDev(
'Function components cannot be given refs. ' +
'Attempts to access this ref will fail. ' +
'Did you mean to use React.forwardRef()?\n\n' +
'Check the render method ' +
'of `Parent`.\n' +
' in Child (at **)\n' +
' in Parent (at **)',
);
});
it('should use correct name in key warning', async () => { it('should use correct name in key warning', async () => {
function Child() { function Child() {
return <div>{[<span />]}</div>; return <div>{[<span />]}</div>;

View File

@ -369,24 +369,6 @@ describe('ref swapping', () => {
}); });
}).rejects.toThrow('Expected ref to be a function'); }).rejects.toThrow('Expected ref to be a function');
}); });
// @gate !enableRefAsProp && www
it('undefined ref on manually inlined React element triggers error', async () => {
const container = document.createElement('div');
const root = ReactDOMClient.createRoot(container);
await expect(async () => {
await act(() => {
root.render({
$$typeof: Symbol.for('react.element'),
type: 'div',
props: {
ref: undefined,
},
key: null,
});
});
}).rejects.toThrow('Expected ref to be a function');
});
}); });
describe('root level refs', () => { describe('root level refs', () => {

View File

@ -35,11 +35,7 @@ import {
ConcurrentRoot, ConcurrentRoot,
LegacyRoot, LegacyRoot,
} from 'react-reconciler/constants'; } from 'react-reconciler/constants';
import { import {disableLegacyMode, disableStringRefs} from 'shared/ReactFeatureFlags';
enableRefAsProp,
disableLegacyMode,
disableStringRefs,
} from 'shared/ReactFeatureFlags';
import ReactSharedInternals from 'shared/ReactSharedInternals'; import ReactSharedInternals from 'shared/ReactSharedInternals';
import ReactVersion from 'shared/ReactVersion'; import ReactVersion from 'shared/ReactVersion';
@ -833,7 +829,7 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
let currentEventPriority = DefaultEventPriority; let currentEventPriority = DefaultEventPriority;
function createJSXElementForTestComparison(type, props) { function createJSXElementForTestComparison(type, props) {
if (__DEV__ && enableRefAsProp) { if (__DEV__) {
const element = { const element = {
type: type, type: type,
$$typeof: REACT_ELEMENT_TYPE, $$typeof: REACT_ELEMENT_TYPE,

View File

@ -45,7 +45,6 @@ import {
} from './ReactWorkTags'; } from './ReactWorkTags';
import isArray from 'shared/isArray'; import isArray from 'shared/isArray';
import { import {
enableRefAsProp,
enableAsyncIterableChildren, enableAsyncIterableChildren,
disableLegacyMode, disableLegacyMode,
enableOwnerStacks, enableOwnerStacks,
@ -239,21 +238,6 @@ function validateFragmentProps(
break; break;
} }
} }
if (!enableRefAsProp && element.ref !== null) {
if (fiber === null) {
// For unkeyed root fragments there's no Fiber. We create a fake one just for
// error stack handling.
fiber = createFiberFromElement(element, returnFiber.mode, 0);
if (__DEV__) {
fiber._debugInfo = currentDebugInfo;
}
fiber.return = returnFiber;
}
runWithFiberInDEV(fiber, () => {
console.error('Invalid attribute `ref` supplied to `React.Fragment`.');
});
}
} }
} }
@ -266,27 +250,14 @@ function unwrapThenable<T>(thenable: Thenable<T>): T {
return trackUsedThenable(thenableState, thenable, index); return trackUsedThenable(thenableState, thenable, index);
} }
function coerceRef( function coerceRef(workInProgress: Fiber, element: ReactElement): void {
returnFiber: Fiber, // TODO: This is a temporary, intermediate step. Now that enableRefAsProp is on,
current: Fiber | null, // we should resolve the `ref` prop during the begin phase of the component
workInProgress: Fiber, // it's attached to (HostComponent, ClassComponent, etc).
element: ReactElement, const refProp = element.props.ref;
): void { // TODO: With enableRefAsProp now rolled out, we shouldn't use the `ref` field. We
let ref;
if (enableRefAsProp) {
// TODO: This is a temporary, intermediate step. When enableRefAsProp is on,
// we should resolve the `ref` prop during the begin phase of the component
// it's attached to (HostComponent, ClassComponent, etc).
const refProp = element.props.ref;
ref = refProp !== undefined ? refProp : null;
} else {
// Old behavior.
ref = element.ref;
}
// TODO: If enableRefAsProp is on, we shouldn't use the `ref` field. We
// should always read the ref from the prop. // should always read the ref from the prop.
workInProgress.ref = ref; workInProgress.ref = refProp !== undefined ? refProp : null;
} }
function throwOnInvalidObjectType(returnFiber: Fiber, newChild: Object) { function throwOnInvalidObjectType(returnFiber: Fiber, newChild: Object) {
@ -569,7 +540,7 @@ function createChildReconciler(
) { ) {
// Move based on index // Move based on index
const existing = useFiber(current, element.props); const existing = useFiber(current, element.props);
coerceRef(returnFiber, current, existing, element); coerceRef(existing, element);
existing.return = returnFiber; existing.return = returnFiber;
if (__DEV__) { if (__DEV__) {
existing._debugOwner = element._owner; existing._debugOwner = element._owner;
@ -580,7 +551,7 @@ function createChildReconciler(
} }
// Insert // Insert
const created = createFiberFromElement(element, returnFiber.mode, lanes); const created = createFiberFromElement(element, returnFiber.mode, lanes);
coerceRef(returnFiber, current, created, element); coerceRef(created, element);
created.return = returnFiber; created.return = returnFiber;
if (__DEV__) { if (__DEV__) {
created._debugInfo = currentDebugInfo; created._debugInfo = currentDebugInfo;
@ -693,7 +664,7 @@ function createChildReconciler(
returnFiber.mode, returnFiber.mode,
lanes, lanes,
); );
coerceRef(returnFiber, null, created, newChild); coerceRef(created, newChild);
created.return = returnFiber; created.return = returnFiber;
if (__DEV__) { if (__DEV__) {
const prevDebugInfo = pushDebugInfo(newChild._debugInfo); const prevDebugInfo = pushDebugInfo(newChild._debugInfo);
@ -1684,7 +1655,7 @@ function createChildReconciler(
) { ) {
deleteRemainingChildren(returnFiber, child.sibling); deleteRemainingChildren(returnFiber, child.sibling);
const existing = useFiber(child, element.props); const existing = useFiber(child, element.props);
coerceRef(returnFiber, child, existing, element); coerceRef(existing, element);
existing.return = returnFiber; existing.return = returnFiber;
if (__DEV__) { if (__DEV__) {
existing._debugOwner = element._owner; existing._debugOwner = element._owner;
@ -1722,7 +1693,7 @@ function createChildReconciler(
return created; return created;
} else { } else {
const created = createFiberFromElement(element, returnFiber.mode, lanes); const created = createFiberFromElement(element, returnFiber.mode, lanes);
coerceRef(returnFiber, currentFirstChild, created, element); coerceRef(created, element);
created.return = returnFiber; created.return = returnFiber;
if (__DEV__) { if (__DEV__) {
created._debugInfo = currentDebugInfo; created._debugInfo = currentDebugInfo;

View File

@ -108,7 +108,6 @@ import {
enableAsyncActions, enableAsyncActions,
enablePostpone, enablePostpone,
enableRenderableContext, enableRenderableContext,
enableRefAsProp,
disableLegacyMode, disableLegacyMode,
disableDefaultPropsExceptForClasses, disableDefaultPropsExceptForClasses,
disableStringRefs, disableStringRefs,
@ -125,10 +124,7 @@ import {
REACT_MEMO_TYPE, REACT_MEMO_TYPE,
getIteratorFn, getIteratorFn,
} from 'shared/ReactSymbols'; } from 'shared/ReactSymbols';
import { import {setCurrentFiber} from './ReactCurrentFiber';
getCurrentFiberOwnerNameInDevOrNull,
setCurrentFiber,
} from './ReactCurrentFiber';
import { import {
resolveFunctionForHotReloading, resolveFunctionForHotReloading,
resolveForwardRefForHotReloading, resolveForwardRefForHotReloading,
@ -319,7 +315,6 @@ let didWarnAboutBadClass;
let didWarnAboutContextTypeOnFunctionComponent; let didWarnAboutContextTypeOnFunctionComponent;
let didWarnAboutContextTypes; let didWarnAboutContextTypes;
let didWarnAboutGetDerivedStateOnFunctionComponent; let didWarnAboutGetDerivedStateOnFunctionComponent;
let didWarnAboutFunctionRefs;
export let didWarnAboutReassigningProps: boolean; export let didWarnAboutReassigningProps: boolean;
let didWarnAboutRevealOrder; let didWarnAboutRevealOrder;
let didWarnAboutTailOptions; let didWarnAboutTailOptions;
@ -330,7 +325,6 @@ if (__DEV__) {
didWarnAboutContextTypeOnFunctionComponent = ({}: {[string]: boolean}); didWarnAboutContextTypeOnFunctionComponent = ({}: {[string]: boolean});
didWarnAboutContextTypes = ({}: {[string]: boolean}); didWarnAboutContextTypes = ({}: {[string]: boolean});
didWarnAboutGetDerivedStateOnFunctionComponent = ({}: {[string]: boolean}); didWarnAboutGetDerivedStateOnFunctionComponent = ({}: {[string]: boolean});
didWarnAboutFunctionRefs = ({}: {[string]: boolean});
didWarnAboutReassigningProps = false; didWarnAboutReassigningProps = false;
didWarnAboutRevealOrder = ({}: {[empty]: boolean}); didWarnAboutRevealOrder = ({}: {[empty]: boolean});
didWarnAboutTailOptions = ({}: {[string]: boolean}); didWarnAboutTailOptions = ({}: {[string]: boolean});
@ -416,7 +410,7 @@ function updateForwardRef(
const ref = workInProgress.ref; const ref = workInProgress.ref;
let propsWithoutRef; let propsWithoutRef;
if (enableRefAsProp && 'ref' in nextProps) { if ('ref' in nextProps) {
// `ref` is just a prop now, but `forwardRef` expects it to not appear in // `ref` is just a prop now, but `forwardRef` expects it to not appear in
// the props object. This used to happen in the JSX runtime, but now we do // the props object. This used to happen in the JSX runtime, but now we do
// it here. // it here.
@ -1954,25 +1948,6 @@ function validateFunctionComponentInDev(workInProgress: Fiber, Component: any) {
Component.displayName || Component.name || 'Component', Component.displayName || Component.name || 'Component',
); );
} }
if (!enableRefAsProp && workInProgress.ref !== null) {
let info = '';
const componentName = getComponentNameFromType(Component) || 'Unknown';
const ownerName = getCurrentFiberOwnerNameInDevOrNull();
if (ownerName) {
info += '\n\nCheck the render method of `' + ownerName + '`.';
}
const warningKey = componentName + '|' + (ownerName || '');
if (!didWarnAboutFunctionRefs[warningKey]) {
didWarnAboutFunctionRefs[warningKey] = true;
console.error(
'Function components cannot be given refs. ' +
'Attempts to access this ref will fail. ' +
'Did you mean to use React.forwardRef()?%s',
info,
);
}
}
if ( if (
!disableDefaultPropsExceptForClasses && !disableDefaultPropsExceptForClasses &&

View File

@ -23,7 +23,6 @@ import {
enableDebugTracing, enableDebugTracing,
enableSchedulingProfiler, enableSchedulingProfiler,
enableLazyContextPropagation, enableLazyContextPropagation,
enableRefAsProp,
disableDefaultPropsExceptForClasses, disableDefaultPropsExceptForClasses,
} from 'shared/ReactFeatureFlags'; } from 'shared/ReactFeatureFlags';
import ReactStrictModeWarnings from './ReactStrictModeWarnings'; import ReactStrictModeWarnings from './ReactStrictModeWarnings';
@ -1252,14 +1251,12 @@ export function resolveClassComponentProps(
): Object { ): Object {
let newProps = baseProps; let newProps = baseProps;
if (enableRefAsProp) { // Remove ref from the props object, if it exists.
// Remove ref from the props object, if it exists. if ('ref' in baseProps) {
if ('ref' in baseProps) { newProps = ({}: any);
newProps = ({}: any); for (const propName in baseProps) {
for (const propName in baseProps) { if (propName !== 'ref') {
if (propName !== 'ref') { newProps[propName] = baseProps[propName];
newProps[propName] = baseProps[propName];
}
} }
} }
} }

View File

@ -85,7 +85,6 @@ describe('ReactFiberRefs', () => {
expect(ref2.current).not.toBe(null); expect(ref2.current).not.toBe(null);
}); });
// @gate enableRefAsProp
// @gate !disableStringRefs // @gate !disableStringRefs
it('string ref props are converted to function refs', async () => { it('string ref props are converted to function refs', async () => {
let refProp; let refProp;
@ -105,7 +104,7 @@ describe('ReactFiberRefs', () => {
const root = ReactNoop.createRoot(); const root = ReactNoop.createRoot();
await act(() => root.render(<Owner />)); await act(() => root.render(<Owner />));
// When string refs aren't disabled, and enableRefAsProp is on, string refs // When string refs aren't disabled, string refs
// the receiving component receives a callback ref, not the original string. // the receiving component receives a callback ref, not the original string.
// This behavior should never be shipped to open source; it's only here to // This behavior should never be shipped to open source; it's only here to
// allow Meta to keep using string refs temporarily while they finish // allow Meta to keep using string refs temporarily while they finish

View File

@ -1299,20 +1299,7 @@ describe('ReactIncrementalSideEffects', () => {
ReactNoop.render(<Foo show={true} />); ReactNoop.render(<Foo show={true} />);
if (gate(flags => flags.enableRefAsProp)) { await waitForAll([]);
await waitForAll([]);
} else {
await expect(async () => await waitForAll([])).toErrorDev(
'Function components cannot be given refs. ' +
'Attempts to access this ref will fail. ' +
'Did you mean to use React.forwardRef()?\n\n' +
'Check the render method ' +
'of `Foo`.\n' +
' in FunctionComponent (at **)\n' +
' in div (at **)\n' +
' in Foo (at **)',
);
}
expect(ops).toEqual([ expect(ops).toEqual([
classInstance, classInstance,

View File

@ -1236,30 +1236,6 @@ describe('ReactLazy', () => {
expect(root).toMatchRenderedOutput('2'); expect(root).toMatchRenderedOutput('2');
}); });
// @gate !enableRefAsProp || !__DEV__
it('warns about ref on functions for lazy-loaded components', async () => {
const Foo = props => <div />;
const LazyFoo = lazy(() => {
return fakeImport(Foo);
});
const ref = React.createRef();
ReactTestRenderer.create(
<Suspense fallback={<Text text="Loading..." />}>
<LazyFoo ref={ref} />
</Suspense>,
{
unstable_isConcurrent: true,
},
);
await waitForAll(['Loading...']);
await resolveFakeImport(Foo);
await expect(async () => {
await waitForAll([]);
}).toErrorDev('Function components cannot be given refs');
});
it('should error with a component stack naming the resolved component', async () => { it('should error with a component stack naming the resolved component', async () => {
let componentStackMessage; let componentStackMessage;

View File

@ -44,43 +44,6 @@ describe('memo', () => {
return {default: result}; return {default: result};
} }
// @gate !enableRefAsProp || !__DEV__
it('warns when giving a ref (simple)', async () => {
// This test lives outside sharedTests because the wrappers don't forward
// refs properly, and they end up affecting the current owner which is used
// by the warning (making the messages not line up).
function App() {
return null;
}
App = React.memo(App);
function Outer() {
return <App ref={() => {}} />;
}
ReactNoop.render(<Outer />);
await expect(async () => await waitForAll([])).toErrorDev([
'Function components cannot be given refs. Attempts to access ' +
'this ref will fail.',
]);
});
// @gate !enableRefAsProp || !__DEV__
it('warns when giving a ref (complex)', async () => {
function App() {
return null;
}
// A custom compare function means this won't use SimpleMemoComponent (as of this writing)
// SimpleMemoComponent is unobservable tho, so we can't check :)
App = React.memo(App, () => false);
function Outer() {
return <App ref={() => {}} />;
}
ReactNoop.render(<Outer />);
await expect(async () => await waitForAll([])).toErrorDev([
'Function components cannot be given refs. Attempts to access ' +
'this ref will fail.',
]);
});
// Tests should run against both the lazy and non-lazy versions of `memo`. // Tests should run against both the lazy and non-lazy versions of `memo`.
// To make the tests work for both versions, we wrap the non-lazy version in // To make the tests work for both versions, we wrap the non-lazy version in
// a lazy function component. // a lazy function component.

View File

@ -160,7 +160,6 @@ import {
enablePostpone, enablePostpone,
enableHalt, enableHalt,
enableRenderableContext, enableRenderableContext,
enableRefAsProp,
disableDefaultPropsExceptForClasses, disableDefaultPropsExceptForClasses,
enableAsyncIterableChildren, enableAsyncIterableChildren,
disableStringRefs, disableStringRefs,
@ -1671,14 +1670,12 @@ export function resolveClassComponentProps(
): Object { ): Object {
let newProps = baseProps; let newProps = baseProps;
if (enableRefAsProp) { // Remove ref from the props object, if it exists.
// Remove ref from the props object, if it exists. if ('ref' in baseProps) {
if ('ref' in baseProps) { newProps = ({}: any);
newProps = ({}: any); for (const propName in baseProps) {
for (const propName in baseProps) { if (propName !== 'ref') {
if (propName !== 'ref') { newProps[propName] = baseProps[propName];
newProps[propName] = baseProps[propName];
}
} }
} }
} }
@ -1973,7 +1970,7 @@ function renderForwardRef(
ref: any, ref: any,
): void { ): void {
let propsWithoutRef; let propsWithoutRef;
if (enableRefAsProp && 'ref' in props) { if ('ref' in props) {
// `ref` is just a prop now, but `forwardRef` expects it to not appear in // `ref` is just a prop now, but `forwardRef` expects it to not appear in
// the props object. This used to happen in the JSX runtime, but now we do // the props object. This used to happen in the JSX runtime, but now we do
// it here. // it here.
@ -2595,16 +2592,10 @@ function retryNode(request: Request, task: Task): void {
const key = element.key; const key = element.key;
const props = element.props; const props = element.props;
let ref; // TODO: We should get the ref off the props object right before using
if (enableRefAsProp) { // it.
// TODO: This is a temporary, intermediate step. Once the feature const refProp = props.ref;
// flag is removed, we should get the ref off the props object right const ref = refProp !== undefined ? refProp : null;
// before using it.
const refProp = props.ref;
ref = refProp !== undefined ? refProp : null;
} else {
ref = element.ref;
}
const debugTask: null | ConsoleTask = const debugTask: null | ConsoleTask =
__DEV__ && enableOwnerStacks ? task.debugTask : null; __DEV__ && enableOwnerStacks ? task.debugTask : null;

View File

@ -18,7 +18,6 @@ import {
enablePostpone, enablePostpone,
enableHalt, enableHalt,
enableTaint, enableTaint,
enableRefAsProp,
enableServerComponentLogs, enableServerComponentLogs,
enableOwnerStacks, enableOwnerStacks,
} from 'shared/ReactFeatureFlags'; } from 'shared/ReactFeatureFlags';
@ -2512,16 +2511,10 @@ function renderModelDestructive(
} }
const props = element.props; const props = element.props;
let ref; // TODO: We should get the ref off the props object right before using
if (enableRefAsProp) { // it.
// TODO: This is a temporary, intermediate step. Once the feature const refProp = props.ref;
// flag is removed, we should get the ref off the props object right const ref = refProp !== undefined ? refProp : null;
// before using it.
const refProp = props.ref;
ref = refProp !== undefined ? refProp : null;
} else {
ref = element.ref;
}
// Attempt to render the Server Component. // Attempt to render the Server Component.

View File

@ -386,39 +386,6 @@ describe('ReactTestRenderer', () => {
expect(log).toEqual([null]); expect(log).toEqual([null]);
}); });
// @gate !enableRefAsProp || !__DEV__
it('warns correctly for refs on SFCs', async () => {
function Bar() {
return <div>Hello, world</div>;
}
class Foo extends React.Component {
fooRef = React.createRef();
render() {
return <Bar ref={this.fooRef} />;
}
}
class Baz extends React.Component {
bazRef = React.createRef();
render() {
return <div ref={this.bazRef} />;
}
}
await act(() => {
ReactTestRenderer.create(<Baz />);
});
await expect(async () => {
await act(() => {
ReactTestRenderer.create(<Foo />);
});
}).toErrorDev(
'Function components cannot be given refs. Attempts ' +
'to access this ref will fail. ' +
'Did you mean to use React.forwardRef()?\n' +
' in Bar (at **)\n' +
' in Foo (at **)',
);
});
it('allows an optional createNodeMock function', async () => { it('allows an optional createNodeMock function', async () => {
const mockDivInstance = {appendChild: () => {}}; const mockDivInstance = {appendChild: () => {}};
const mockInputInstance = {focus: () => {}}; const mockInputInstance = {focus: () => {}};
@ -1226,11 +1193,9 @@ describe('ReactTestRenderer', () => {
{ {
instance: null, instance: null,
nodeType: 'host', nodeType: 'host',
props: gate(flags => flags.enableRefAsProp) props: {
? { ref: refFn,
ref: refFn, },
}
: {},
rendered: [], rendered: [],
type: 'span', type: 'span',
}, },

View File

@ -37,11 +37,7 @@ describe('ReactCreateElement', () => {
const element = React.createElement(ComponentClass); const element = React.createElement(ComponentClass);
expect(element.type).toBe(ComponentClass); expect(element.type).toBe(ComponentClass);
expect(element.key).toBe(null); expect(element.key).toBe(null);
if (gate(flags => flags.enableRefAsProp)) { expect(element.ref).toBe(null);
expect(element.ref).toBe(null);
} else {
expect(element.ref).toBe(null);
}
if (__DEV__) { if (__DEV__) {
expect(Object.isFrozen(element)).toBe(true); expect(Object.isFrozen(element)).toBe(true);
expect(Object.isFrozen(element.props)).toBe(true); expect(Object.isFrozen(element.props)).toBe(true);
@ -90,45 +86,11 @@ describe('ReactCreateElement', () => {
); );
}); });
// @gate !enableRefAsProp || !__DEV__
it('should warn when `ref` is being accessed', async () => {
class Child extends React.Component {
render() {
return React.createElement('div', null, this.props.ref);
}
}
class Parent extends React.Component {
render() {
return React.createElement(
'div',
null,
React.createElement(Child, {ref: React.createRef()}),
);
}
}
const root = ReactDOMClient.createRoot(document.createElement('div'));
await expect(async () => {
await act(() => {
root.render(React.createElement(Parent));
});
}).toErrorDev(
'Child: `ref` is not a prop. Trying to access it will result ' +
'in `undefined` being returned. If you need to access the same ' +
'value within the child component, you should pass it as a different ' +
'prop. (https://react.dev/link/special-props)',
);
});
it('allows a string to be passed as the type', () => { it('allows a string to be passed as the type', () => {
const element = React.createElement('div'); const element = React.createElement('div');
expect(element.type).toBe('div'); expect(element.type).toBe('div');
expect(element.key).toBe(null); expect(element.key).toBe(null);
if (gate(flags => flags.enableRefAsProp)) { expect(element.ref).toBe(null);
expect(element.ref).toBe(null);
} else {
expect(element.ref).toBe(null);
}
if (__DEV__) { if (__DEV__) {
expect(Object.isFrozen(element)).toBe(true); expect(Object.isFrozen(element)).toBe(true);
expect(Object.isFrozen(element.props)).toBe(true); expect(Object.isFrozen(element.props)).toBe(true);
@ -179,20 +141,13 @@ describe('ReactCreateElement', () => {
foo: '56', foo: '56',
}); });
expect(element.type).toBe(ComponentClass); expect(element.type).toBe(ComponentClass);
if (gate(flags => flags.enableRefAsProp)) { expect(() => expect(element.ref).toBe(ref)).toErrorDev(
expect(() => expect(element.ref).toBe(ref)).toErrorDev( 'Accessing element.ref was removed in React 19',
'Accessing element.ref was removed in React 19', {withoutStack: true},
{withoutStack: true}, );
); const expectation = {foo: '56', ref};
const expectation = {foo: '56', ref}; Object.freeze(expectation);
Object.freeze(expectation); expect(element.props).toEqual(expectation);
expect(element.props).toEqual(expectation);
} else {
const expectation = {foo: '56'};
Object.freeze(expectation);
expect(element.props).toEqual(expectation);
expect(element.ref).toBe(ref);
}
}); });
it('extracts null key', () => { it('extracts null key', () => {
@ -218,11 +173,7 @@ describe('ReactCreateElement', () => {
const element = React.createElement(ComponentClass, props); const element = React.createElement(ComponentClass, props);
expect(element.type).toBe(ComponentClass); expect(element.type).toBe(ComponentClass);
expect(element.key).toBe(null); expect(element.key).toBe(null);
if (gate(flags => flags.enableRefAsProp)) { expect(element.ref).toBe(null);
expect(element.ref).toBe(null);
} else {
expect(element.ref).toBe(null);
}
if (__DEV__) { if (__DEV__) {
expect(Object.isFrozen(element)).toBe(true); expect(Object.isFrozen(element)).toBe(true);
expect(Object.isFrozen(element.props)).toBe(true); expect(Object.isFrozen(element.props)).toBe(true);
@ -234,11 +185,7 @@ describe('ReactCreateElement', () => {
const elementA = React.createElement('div'); const elementA = React.createElement('div');
const elementB = React.createElement('div', elementA.props); const elementB = React.createElement('div', elementA.props);
expect(elementB.key).toBe(null); expect(elementB.key).toBe(null);
if (gate(flags => flags.enableRefAsProp)) { expect(elementB.ref).toBe(null);
expect(elementB.ref).toBe(null);
} else {
expect(elementB.ref).toBe(null);
}
}); });
it('coerces the key to a string', () => { it('coerces the key to a string', () => {
@ -248,11 +195,7 @@ describe('ReactCreateElement', () => {
}); });
expect(element.type).toBe(ComponentClass); expect(element.type).toBe(ComponentClass);
expect(element.key).toBe('12'); expect(element.key).toBe('12');
if (gate(flags => flags.enableRefAsProp)) { expect(element.ref).toBe(null);
expect(element.ref).toBe(null);
} else {
expect(element.ref).toBe(null);
}
if (__DEV__) { if (__DEV__) {
expect(Object.isFrozen(element)).toBe(true); expect(Object.isFrozen(element)).toBe(true);
expect(Object.isFrozen(element.props)).toBe(true); expect(Object.isFrozen(element.props)).toBe(true);

View File

@ -212,11 +212,7 @@ describe('ReactElementClone', () => {
ref: this.xyzRef, ref: this.xyzRef,
}); });
expect(clone.key).toBe('xyz'); expect(clone.key).toBe('xyz');
if (gate(flags => flags.enableRefAsProp)) { expect(clone.props.ref).toBe(this.xyzRef);
expect(clone.props.ref).toBe(this.xyzRef);
} else {
expect(clone.ref).toBe(this.xyzRef);
}
return <div>{clone}</div>; return <div>{clone}</div>;
} }
} }
@ -274,17 +270,13 @@ describe('ReactElementClone', () => {
const root = ReactDOMClient.createRoot(document.createElement('div')); const root = ReactDOMClient.createRoot(document.createElement('div'));
await act(() => root.render(<Grandparent />)); await act(() => root.render(<Grandparent />));
if (gate(flags => flags.enableRefAsProp && flags.disableStringRefs)) { if (gate(flags => flags.disableStringRefs)) {
expect(component.childRef).toEqual({current: null}); expect(component.childRef).toEqual({current: null});
expect(component.parentRef.current.xyzRef.current.tagName).toBe('SPAN'); expect(component.parentRef.current.xyzRef.current.tagName).toBe('SPAN');
} else if ( } else if (gate(flags => false)) {
gate(flags => !flags.enableRefAsProp && !flags.disableStringRefs)
) {
expect(component.childRef).toEqual({current: null}); expect(component.childRef).toEqual({current: null});
expect(component.parentRef.current.xyzRef.current.tagName).toBe('SPAN'); expect(component.parentRef.current.xyzRef.current.tagName).toBe('SPAN');
} else if ( } else if (gate(flags => !flags.disableStringRefs)) {
gate(flags => flags.enableRefAsProp && !flags.disableStringRefs)
) {
expect(component.childRef).toEqual({current: null}); expect(component.childRef).toEqual({current: null});
expect(component.parentRef.current.xyzRef.current.tagName).toBe('SPAN'); expect(component.parentRef.current.xyzRef.current.tagName).toBe('SPAN');
} else { } else {
@ -397,11 +389,7 @@ describe('ReactElementClone', () => {
const elementA = React.createElement('div'); const elementA = React.createElement('div');
const elementB = React.cloneElement(elementA, elementA.props); const elementB = React.cloneElement(elementA, elementA.props);
expect(elementB.key).toBe(null); expect(elementB.key).toBe(null);
if (gate(flags => flags.enableRefAsProp)) { expect(elementB.ref).toBe(null);
expect(elementB.ref).toBe(null);
} else {
expect(elementB.ref).toBe(null);
}
}); });
it('should ignore undefined key and ref', () => { it('should ignore undefined key and ref', () => {
@ -418,21 +406,17 @@ describe('ReactElementClone', () => {
const clone = React.cloneElement(element, props); const clone = React.cloneElement(element, props);
expect(clone.type).toBe(ComponentClass); expect(clone.type).toBe(ComponentClass);
expect(clone.key).toBe('12'); expect(clone.key).toBe('12');
if (gate(flags => flags.enableRefAsProp && flags.disableStringRefs)) { if (gate(flags => flags.disableStringRefs)) {
expect(clone.props.ref).toBe('34'); expect(clone.props.ref).toBe('34');
expect(() => expect(clone.ref).toBe('34')).toErrorDev( expect(() => expect(clone.ref).toBe('34')).toErrorDev(
'Accessing element.ref was removed in React 19', 'Accessing element.ref was removed in React 19',
{withoutStack: true}, {withoutStack: true},
); );
expect(clone.props).toEqual({foo: 'ef', ref: '34'}); expect(clone.props).toEqual({foo: 'ef', ref: '34'});
} else if ( } else if (gate(flags => false)) {
gate(flags => !flags.enableRefAsProp && !flags.disableStringRefs)
) {
expect(clone.ref).toBe(element.ref); expect(clone.ref).toBe(element.ref);
expect(clone.props).toEqual({foo: 'ef'}); expect(clone.props).toEqual({foo: 'ef'});
} else if ( } else if (gate(flags => !flags.disableStringRefs)) {
gate(flags => flags.enableRefAsProp && !flags.disableStringRefs)
) {
expect(() => { expect(() => {
expect(clone.ref).toBe(element.ref); expect(clone.ref).toBe(element.ref);
}).toErrorDev('Accessing element.ref was removed in React 19', { }).toErrorDev('Accessing element.ref was removed in React 19', {
@ -462,14 +446,8 @@ describe('ReactElementClone', () => {
const clone = React.cloneElement(element, props); const clone = React.cloneElement(element, props);
expect(clone.type).toBe(ComponentClass); expect(clone.type).toBe(ComponentClass);
expect(clone.key).toBe('null'); expect(clone.key).toBe('null');
if (gate(flags => flags.enableRefAsProp)) { expect(clone.ref).toBe(null);
expect(clone.ref).toBe(null); expect(clone.props).toEqual({foo: 'ef', ref: null});
expect(clone.props).toEqual({foo: 'ef', ref: null});
} else {
expect(clone.ref).toBe(null);
expect(clone.props).toEqual({foo: 'ef'});
}
if (__DEV__) { if (__DEV__) {
expect(Object.isFrozen(element)).toBe(true); expect(Object.isFrozen(element)).toBe(true);
expect(Object.isFrozen(element.props)).toBe(true); expect(Object.isFrozen(element.props)).toBe(true);

View File

@ -248,23 +248,13 @@ describe('ReactJSXElementValidator', () => {
} }
} }
if (gate(flags => flags.enableRefAsProp)) { await expect(async () => {
await expect(async () => { const container = document.createElement('div');
const container = document.createElement('div'); const root = ReactDOMClient.createRoot(container);
const root = ReactDOMClient.createRoot(container); await act(() => {
await act(() => { root.render(<Foo />);
root.render(<Foo />); });
}); }).toErrorDev('Invalid prop `ref` supplied to `React.Fragment`.');
}).toErrorDev('Invalid prop `ref` supplied to `React.Fragment`.');
} else {
await expect(async () => {
const container = document.createElement('div');
const root = ReactDOMClient.createRoot(container);
await act(() => {
root.render(<Foo />);
});
}).toErrorDev('Invalid attribute `ref` supplied to `React.Fragment`.');
}
}); });
it('does not warn for fragments of multiple elements without keys', async () => { it('does not warn for fragments of multiple elements without keys', async () => {

View File

@ -244,34 +244,6 @@ describe('ReactJSXRuntime', () => {
); );
}); });
// @gate !enableRefAsProp || !__DEV__
it('should warn when `ref` is being accessed', async () => {
const container = document.createElement('div');
class Child extends React.Component {
render() {
return JSXRuntime.jsx('div', {children: this.props.ref});
}
}
class Parent extends React.Component {
render() {
return JSXRuntime.jsx('div', {
children: JSXRuntime.jsx(Child, {ref: React.createRef()}),
});
}
}
await expect(async () => {
const root = ReactDOMClient.createRoot(container);
await act(() => {
root.render(JSXRuntime.jsx(Parent, {}));
});
}).toErrorDev(
'Child: `ref` is not a prop. Trying to access it will result ' +
'in `undefined` being returned. If you need to access the same ' +
'value within the child component, you should pass it as a different ' +
'prop. (https://react.dev/link/special-props)',
);
});
it('should warn when unkeyed children are passed to jsx', async () => { it('should warn when unkeyed children are passed to jsx', async () => {
const container = document.createElement('div'); const container = document.createElement('div');
@ -377,7 +349,6 @@ describe('ReactJSXRuntime', () => {
expect(didCall).toBe(false); expect(didCall).toBe(false);
}); });
// @gate enableRefAsProp
it('does not clone props object if key and ref is not spread', async () => { it('does not clone props object if key and ref is not spread', async () => {
const config = { const config = {
foo: 'foo', foo: 'foo',

View File

@ -55,11 +55,7 @@ describe('ReactJSXTransformIntegration', () => {
const element = <Component />; const element = <Component />;
expect(element.type).toBe(Component); expect(element.type).toBe(Component);
expect(element.key).toBe(null); expect(element.key).toBe(null);
if (gate(flags => flags.enableRefAsProp)) { expect(element.ref).toBe(null);
expect(element.ref).toBe(null);
} else {
expect(element.ref).toBe(null);
}
const expectation = {}; const expectation = {};
Object.freeze(expectation); Object.freeze(expectation);
expect(element.props).toEqual(expectation); expect(element.props).toEqual(expectation);
@ -69,11 +65,7 @@ describe('ReactJSXTransformIntegration', () => {
const element = <div />; const element = <div />;
expect(element.type).toBe('div'); expect(element.type).toBe('div');
expect(element.key).toBe(null); expect(element.key).toBe(null);
if (gate(flags => flags.enableRefAsProp)) { expect(element.ref).toBe(null);
expect(element.ref).toBe(null);
} else {
expect(element.ref).toBe(null);
}
const expectation = {}; const expectation = {};
Object.freeze(expectation); Object.freeze(expectation);
expect(element.props).toEqual(expectation); expect(element.props).toEqual(expectation);
@ -84,11 +76,7 @@ describe('ReactJSXTransformIntegration', () => {
const element = <TagName />; const element = <TagName />;
expect(element.type).toBe('div'); expect(element.type).toBe('div');
expect(element.key).toBe(null); expect(element.key).toBe(null);
if (gate(flags => flags.enableRefAsProp)) { expect(element.ref).toBe(null);
expect(element.ref).toBe(null);
} else {
expect(element.ref).toBe(null);
}
const expectation = {}; const expectation = {};
Object.freeze(expectation); Object.freeze(expectation);
expect(element.props).toEqual(expectation); expect(element.props).toEqual(expectation);
@ -124,31 +112,20 @@ describe('ReactJSXTransformIntegration', () => {
const ref = React.createRef(); const ref = React.createRef();
const element = <Component ref={ref} foo="56" />; const element = <Component ref={ref} foo="56" />;
expect(element.type).toBe(Component); expect(element.type).toBe(Component);
if (gate(flags => flags.enableRefAsProp)) { expect(() => expect(element.ref).toBe(ref)).toErrorDev(
expect(() => expect(element.ref).toBe(ref)).toErrorDev( 'Accessing element.ref was removed in React 19',
'Accessing element.ref was removed in React 19', {withoutStack: true},
{withoutStack: true}, );
); const expectation = {foo: '56', ref};
const expectation = {foo: '56', ref}; Object.freeze(expectation);
Object.freeze(expectation); expect(element.props).toEqual(expectation);
expect(element.props).toEqual(expectation);
} else {
const expectation = {foo: '56'};
Object.freeze(expectation);
expect(element.props).toEqual(expectation);
expect(element.ref).toBe(ref);
}
}); });
it('coerces the key to a string', () => { it('coerces the key to a string', () => {
const element = <Component key={12} foo="56" />; const element = <Component key={12} foo="56" />;
expect(element.type).toBe(Component); expect(element.type).toBe(Component);
expect(element.key).toBe('12'); expect(element.key).toBe('12');
if (gate(flags => flags.enableRefAsProp)) { expect(element.ref).toBe(null);
expect(element.ref).toBe(null);
} else {
expect(element.ref).toBe(null);
}
const expectation = {foo: '56'}; const expectation = {foo: '56'};
Object.freeze(expectation); Object.freeze(expectation);
expect(element.props).toEqual(expectation); expect(element.props).toEqual(expectation);

View File

@ -20,7 +20,6 @@ import isValidElementType from 'shared/isValidElementType';
import isArray from 'shared/isArray'; import isArray from 'shared/isArray';
import {describeUnknownElementTypeFrameInDEV} from 'shared/ReactComponentStackFrame'; import {describeUnknownElementTypeFrameInDEV} from 'shared/ReactComponentStackFrame';
import { import {
enableRefAsProp,
disableStringRefs, disableStringRefs,
disableDefaultPropsExceptForClasses, disableDefaultPropsExceptForClasses,
enableOwnerStacks, enableOwnerStacks,
@ -72,7 +71,6 @@ function getOwner() {
} }
let specialPropKeyWarningShown; let specialPropKeyWarningShown;
let specialPropRefWarningShown;
let didWarnAboutStringRefs; let didWarnAboutStringRefs;
let didWarnAboutElementRef; let didWarnAboutElementRef;
let didWarnAboutOldJSXRuntime; let didWarnAboutOldJSXRuntime;
@ -82,7 +80,7 @@ if (__DEV__ || enableLogStringRefsProd) {
didWarnAboutElementRef = {}; didWarnAboutElementRef = {};
} }
const enableFastJSXWithoutStringRefs = enableRefAsProp && disableStringRefs; const enableFastJSXWithoutStringRefs = disableStringRefs;
function hasValidRef(config) { function hasValidRef(config) {
if (__DEV__) { if (__DEV__) {
@ -159,30 +157,6 @@ function defineKeyPropWarningGetter(props, displayName) {
} }
} }
function defineRefPropWarningGetter(props, displayName) {
if (!enableRefAsProp) {
if (__DEV__) {
const warnAboutAccessingRef = function () {
if (!specialPropRefWarningShown) {
specialPropRefWarningShown = true;
console.error(
'%s: `ref` is not a prop. Trying to access it will result ' +
'in `undefined` being returned. If you need to access the same ' +
'value within the child component, you should pass it as a different ' +
'prop. (https://react.dev/link/special-props)',
displayName,
);
}
};
warnAboutAccessingRef.isReactWarning = true;
Object.defineProperty(props, 'ref', {
get: warnAboutAccessingRef,
configurable: true,
});
}
}
}
function elementRefGetterWithDeprecationWarning() { function elementRefGetterWithDeprecationWarning() {
if (__DEV__) { if (__DEV__) {
const componentName = getComponentNameFromType(this.type); const componentName = getComponentNameFromType(this.type);
@ -225,7 +199,6 @@ function elementRefGetterWithDeprecationWarning() {
function ReactElement( function ReactElement(
type, type,
key, key,
_ref,
self, self,
source, source,
owner, owner,
@ -233,24 +206,18 @@ function ReactElement(
debugStack, debugStack,
debugTask, debugTask,
) { ) {
let ref; // Ignore whatever was passed as the ref argument and treat `props.ref` as
if (enableRefAsProp) { // the source of truth. The only thing we use this for is `element.ref`,
// When enableRefAsProp is on, ignore whatever was passed as the ref // which will log a deprecation warning on access. In the next release, we
// argument and treat `props.ref` as the source of truth. The only thing we // can remove `element.ref` as well as the `ref` argument.
// use this for is `element.ref`, which will log a deprecation warning on const refProp = props.ref;
// access. In the next release, we can remove `element.ref` as well as the
// `ref` argument.
const refProp = props.ref;
// An undefined `element.ref` is coerced to `null` for // An undefined `element.ref` is coerced to `null` for
// backwards compatibility. // backwards compatibility.
ref = refProp !== undefined ? refProp : null; const ref = refProp !== undefined ? refProp : null;
} else {
ref = _ref;
}
let element; let element;
if (__DEV__ && enableRefAsProp) { if (__DEV__) {
// In dev, make `ref` a non-enumerable property with a warning. It's non- // In dev, make `ref` a non-enumerable property with a warning. It's non-
// enumerable so that test matchers and serializers don't access it and // enumerable so that test matchers and serializers don't access it and
// trigger the warning. // trigger the warning.
@ -380,7 +347,6 @@ function ReactElement(
*/ */
export function jsxProd(type, config, maybeKey) { export function jsxProd(type, config, maybeKey) {
let key = null; let key = null;
let ref = null;
// Currently, key can be spread in as a prop. This causes a potential // Currently, key can be spread in as a prop. This causes a potential
// issue if key is also explicitly declared (ie. <div {...props} key="Hi" /> // issue if key is also explicitly declared (ie. <div {...props} key="Hi" />
@ -402,19 +368,9 @@ export function jsxProd(type, config, maybeKey) {
key = '' + config.key; key = '' + config.key;
} }
if (hasValidRef(config)) {
if (!enableRefAsProp) {
ref = config.ref;
if (!disableStringRefs) {
ref = coerceStringRef(ref, getOwner(), type);
}
}
}
let props; let props;
if ( if (
(enableFastJSXWithoutStringRefs || (enableFastJSXWithoutStringRefs || !('ref' in config)) &&
(enableRefAsProp && !('ref' in config))) &&
!('key' in config) !('key' in config)
) { ) {
// If key was not spread in, we can reuse the original props object. This // If key was not spread in, we can reuse the original props object. This
@ -434,8 +390,8 @@ export function jsxProd(type, config, maybeKey) {
props = {}; props = {};
for (const propName in config) { for (const propName in config) {
// Skip over reserved prop names // Skip over reserved prop names
if (propName !== 'key' && (enableRefAsProp || propName !== 'ref')) { if (propName !== 'key') {
if (enableRefAsProp && !disableStringRefs && propName === 'ref') { if (!disableStringRefs && propName === 'ref') {
props.ref = coerceStringRef(config[propName], getOwner(), type); props.ref = coerceStringRef(config[propName], getOwner(), type);
} else { } else {
props[propName] = config[propName]; props[propName] = config[propName];
@ -459,7 +415,6 @@ export function jsxProd(type, config, maybeKey) {
return ReactElement( return ReactElement(
type, type,
key, key,
ref,
undefined, undefined,
undefined, undefined,
getOwner(), getOwner(),
@ -662,7 +617,6 @@ function jsxDEVImpl(
} }
let key = null; let key = null;
let ref = null;
// Currently, key can be spread in as a prop. This causes a potential // Currently, key can be spread in as a prop. This causes a potential
// issue if key is also explicitly declared (ie. <div {...props} key="Hi" /> // issue if key is also explicitly declared (ie. <div {...props} key="Hi" />
@ -684,22 +638,15 @@ function jsxDEVImpl(
key = '' + config.key; key = '' + config.key;
} }
if (hasValidRef(config)) { if (!disableStringRefs) {
if (!enableRefAsProp) { if (hasValidRef(config)) {
ref = config.ref;
if (!disableStringRefs) {
ref = coerceStringRef(ref, getOwner(), type);
}
}
if (!disableStringRefs) {
warnIfStringRefCannotBeAutoConverted(config, self); warnIfStringRefCannotBeAutoConverted(config, self);
} }
} }
let props; let props;
if ( if (
(enableFastJSXWithoutStringRefs || (enableFastJSXWithoutStringRefs || !('ref' in config)) &&
(enableRefAsProp && !('ref' in config))) &&
!('key' in config) !('key' in config)
) { ) {
// If key was not spread in, we can reuse the original props object. This // If key was not spread in, we can reuse the original props object. This
@ -719,8 +666,8 @@ function jsxDEVImpl(
props = {}; props = {};
for (const propName in config) { for (const propName in config) {
// Skip over reserved prop names // Skip over reserved prop names
if (propName !== 'key' && (enableRefAsProp || propName !== 'ref')) { if (propName !== 'key') {
if (enableRefAsProp && !disableStringRefs && propName === 'ref') { if (!disableStringRefs && propName === 'ref') {
props.ref = coerceStringRef(config[propName], getOwner(), type); props.ref = coerceStringRef(config[propName], getOwner(), type);
} else { } else {
props[propName] = config[propName]; props[propName] = config[propName];
@ -741,23 +688,17 @@ function jsxDEVImpl(
} }
} }
if (key || (!enableRefAsProp && ref)) { if (key) {
const displayName = const displayName =
typeof type === 'function' typeof type === 'function'
? type.displayName || type.name || 'Unknown' ? type.displayName || type.name || 'Unknown'
: type; : type;
if (key) { defineKeyPropWarningGetter(props, displayName);
defineKeyPropWarningGetter(props, displayName);
}
if (!enableRefAsProp && ref) {
defineRefPropWarningGetter(props, displayName);
}
} }
return ReactElement( return ReactElement(
type, type,
key, key,
ref,
self, self,
source, source,
getOwner(), getOwner(),
@ -838,7 +779,6 @@ export function createElement(type, config, children) {
const props = {}; const props = {};
let key = null; let key = null;
let ref = null;
if (config != null) { if (config != null) {
if (__DEV__) { if (__DEV__) {
@ -861,15 +801,8 @@ export function createElement(type, config, children) {
} }
} }
if (hasValidRef(config)) { if (__DEV__ && !disableStringRefs) {
if (!enableRefAsProp) { if (hasValidRef(config)) {
ref = config.ref;
if (!disableStringRefs) {
ref = coerceStringRef(ref, getOwner(), type);
}
}
if (__DEV__ && !disableStringRefs) {
warnIfStringRefCannotBeAutoConverted(config, config.__self); warnIfStringRefCannotBeAutoConverted(config, config.__self);
} }
} }
@ -886,7 +819,6 @@ export function createElement(type, config, children) {
hasOwnProperty.call(config, propName) && hasOwnProperty.call(config, propName) &&
// Skip over reserved prop names // Skip over reserved prop names
propName !== 'key' && propName !== 'key' &&
(enableRefAsProp || propName !== 'ref') &&
// Even though we don't use these anymore in the runtime, we don't want // Even though we don't use these anymore in the runtime, we don't want
// them to appear as props, so in createElement we filter them out. // them to appear as props, so in createElement we filter them out.
// We don't have to do this in the jsx() runtime because the jsx() // We don't have to do this in the jsx() runtime because the jsx()
@ -894,7 +826,7 @@ export function createElement(type, config, children) {
propName !== '__self' && propName !== '__self' &&
propName !== '__source' propName !== '__source'
) { ) {
if (enableRefAsProp && !disableStringRefs && propName === 'ref') { if (!disableStringRefs && propName === 'ref') {
props.ref = coerceStringRef(config[propName], getOwner(), type); props.ref = coerceStringRef(config[propName], getOwner(), type);
} else { } else {
props[propName] = config[propName]; props[propName] = config[propName];
@ -931,24 +863,18 @@ export function createElement(type, config, children) {
} }
} }
if (__DEV__) { if (__DEV__) {
if (key || (!enableRefAsProp && ref)) { if (key) {
const displayName = const displayName =
typeof type === 'function' typeof type === 'function'
? type.displayName || type.name || 'Unknown' ? type.displayName || type.name || 'Unknown'
: type; : type;
if (key) { defineKeyPropWarningGetter(props, displayName);
defineKeyPropWarningGetter(props, displayName);
}
if (!enableRefAsProp && ref) {
defineRefPropWarningGetter(props, displayName);
}
} }
} }
return ReactElement( return ReactElement(
type, type,
key, key,
ref,
undefined, undefined,
undefined, undefined,
getOwner(), getOwner(),
@ -962,9 +888,6 @@ export function cloneAndReplaceKey(oldElement, newKey) {
const clonedElement = ReactElement( const clonedElement = ReactElement(
oldElement.type, oldElement.type,
newKey, newKey,
// When enableRefAsProp is on, this argument is ignored. This check only
// exists to avoid the `ref` access warning.
enableRefAsProp ? null : oldElement.ref,
undefined, undefined,
undefined, undefined,
!__DEV__ && disableStringRefs ? undefined : oldElement._owner, !__DEV__ && disableStringRefs ? undefined : oldElement._owner,
@ -997,7 +920,6 @@ export function cloneElement(element, config, children) {
// Reserved names are extracted // Reserved names are extracted
let key = element.key; let key = element.key;
let ref = enableRefAsProp ? null : element.ref;
// Owner will be preserved, unless ref is overridden // Owner will be preserved, unless ref is overridden
let owner = !__DEV__ && disableStringRefs ? undefined : element._owner; let owner = !__DEV__ && disableStringRefs ? undefined : element._owner;
@ -1005,13 +927,6 @@ export function cloneElement(element, config, children) {
if (config != null) { if (config != null) {
if (hasValidRef(config)) { if (hasValidRef(config)) {
owner = __DEV__ || !disableStringRefs ? getOwner() : undefined; owner = __DEV__ || !disableStringRefs ? getOwner() : undefined;
if (!enableRefAsProp) {
// Silently steal the ref from the parent.
ref = config.ref;
if (!disableStringRefs) {
ref = coerceStringRef(ref, owner, element.type);
}
}
} }
if (hasValidKey(config)) { if (hasValidKey(config)) {
if (__DEV__) { if (__DEV__) {
@ -1034,7 +949,6 @@ export function cloneElement(element, config, children) {
hasOwnProperty.call(config, propName) && hasOwnProperty.call(config, propName) &&
// Skip over reserved prop names // Skip over reserved prop names
propName !== 'key' && propName !== 'key' &&
(enableRefAsProp || propName !== 'ref') &&
// ...and maybe these, too, though we currently rely on them for // ...and maybe these, too, though we currently rely on them for
// warnings and debug information in dev. Need to decide if we're OK // warnings and debug information in dev. Need to decide if we're OK
// with dropping them. In the jsx() runtime it's not an issue because // with dropping them. In the jsx() runtime it's not an issue because
@ -1046,7 +960,7 @@ export function cloneElement(element, config, children) {
// Undefined `ref` is ignored by cloneElement. We treat it the same as // Undefined `ref` is ignored by cloneElement. We treat it the same as
// if the property were missing. This is mostly for // if the property were missing. This is mostly for
// backwards compatibility. // backwards compatibility.
!(enableRefAsProp && propName === 'ref' && config.ref === undefined) !(propName === 'ref' && config.ref === undefined)
) { ) {
if ( if (
!disableDefaultPropsExceptForClasses && !disableDefaultPropsExceptForClasses &&
@ -1056,7 +970,7 @@ export function cloneElement(element, config, children) {
// Resolve default props // Resolve default props
props[propName] = defaultProps[propName]; props[propName] = defaultProps[propName];
} else { } else {
if (enableRefAsProp && !disableStringRefs && propName === 'ref') { if (!disableStringRefs && propName === 'ref') {
props.ref = coerceStringRef(config[propName], owner, element.type); props.ref = coerceStringRef(config[propName], owner, element.type);
} else { } else {
props[propName] = config[propName]; props[propName] = config[propName];
@ -1082,7 +996,6 @@ export function cloneElement(element, config, children) {
const clonedElement = ReactElement( const clonedElement = ReactElement(
element.type, element.type,
key, key,
ref,
undefined, undefined,
undefined, undefined,
owner, owner,

View File

@ -208,13 +208,8 @@ export const enableFilterEmptyStringAttributesDOM = true;
// Disabled caching behavior of `react/cache` in client runtimes. // Disabled caching behavior of `react/cache` in client runtimes.
export const disableClientCache = true; export const disableClientCache = true;
// Subtle breaking changes to JSX runtime to make it faster, like passing `ref`
// as a normal prop instead of stripping it from the props object.
// Passes `ref` as a normal prop instead of stripping it from the props object
// during element creation.
export const enableRefAsProp = true;
export const disableStringRefs = true; export const disableStringRefs = true;
/** /**
* If set to a function, the function will be called with the component name * If set to a function, the function will be called with the component name
* and ref string. * and ref string.

View File

@ -73,7 +73,6 @@ export const enableProfilerCommitHooks = __PROFILE__;
export const enableProfilerNestedUpdatePhase = __PROFILE__; export const enableProfilerNestedUpdatePhase = __PROFILE__;
export const enableProfilerTimer = __PROFILE__; export const enableProfilerTimer = __PROFILE__;
export const enableReactTestRendererWarning = false; export const enableReactTestRendererWarning = false;
export const enableRefAsProp = true;
export const enableRenderableContext = true; export const enableRenderableContext = true;
export const enableRetryLaneExpiration = false; export const enableRetryLaneExpiration = false;
export const enableSchedulingProfiler = __PROFILE__; export const enableSchedulingProfiler = __PROFILE__;

View File

@ -63,7 +63,6 @@ export const enableOwnerStacks = false;
export const enablePersistedModeClonedFlag = false; export const enablePersistedModeClonedFlag = false;
export const enablePostpone = false; export const enablePostpone = false;
export const enableReactTestRendererWarning = false; export const enableReactTestRendererWarning = false;
export const enableRefAsProp = true;
export const enableRenderableContext = true; export const enableRenderableContext = true;
export const enableRetryLaneExpiration = false; export const enableRetryLaneExpiration = false;
export const enableSchedulingProfiler = __PROFILE__; export const enableSchedulingProfiler = __PROFILE__;

View File

@ -91,7 +91,6 @@ export const enableSiblingPrerendering = false;
// We really need to get rid of this whole module. Any test renderer specific // We really need to get rid of this whole module. Any test renderer specific
// flags should be handled by the Fiber config. // flags should be handled by the Fiber config.
// const __NEXT_MAJOR__ = __EXPERIMENTAL__; // const __NEXT_MAJOR__ = __EXPERIMENTAL__;
export const enableRefAsProp = true;
export const disableStringRefs = true; export const disableStringRefs = true;
export const disableLegacyMode = true; export const disableLegacyMode = true;
export const disableLegacyContext = true; export const disableLegacyContext = true;

View File

@ -56,7 +56,6 @@ export const enableProfilerCommitHooks = __PROFILE__;
export const enableProfilerNestedUpdatePhase = __PROFILE__; export const enableProfilerNestedUpdatePhase = __PROFILE__;
export const enableProfilerTimer = __PROFILE__; export const enableProfilerTimer = __PROFILE__;
export const enableReactTestRendererWarning = false; export const enableReactTestRendererWarning = false;
export const enableRefAsProp = true;
export const enableRenderableContext = true; export const enableRenderableContext = true;
export const enableRetryLaneExpiration = false; export const enableRetryLaneExpiration = false;
export const enableSchedulingProfiler = __PROFILE__; export const enableSchedulingProfiler = __PROFILE__;

View File

@ -83,7 +83,6 @@ export const disableClientCache = true;
export const enableServerComponentLogs = true; export const enableServerComponentLogs = true;
export const enableInfiniteRenderLoopDetection = false; export const enableInfiniteRenderLoopDetection = false;
export const enableRefAsProp = true;
export const disableStringRefs = false; export const disableStringRefs = false;
export const enableReactTestRendererWarning = false; export const enableReactTestRendererWarning = false;

View File

@ -101,8 +101,6 @@ export const enableLegacyHidden = true;
export const enableComponentStackLocations = true; export const enableComponentStackLocations = true;
export const enableRefAsProp = true;
export const disableTextareaChildren = __EXPERIMENTAL__; export const disableTextareaChildren = __EXPERIMENTAL__;
export const consoleManagedByDevToolsDuringStrictMode = true; export const consoleManagedByDevToolsDuringStrictMode = true;