mirror of
https://github.com/zebrajr/react.git
synced 2025-12-06 00:20:04 +01:00
Remove enableRefAsProp feature flag (#30346)
The flag is fully rolled out.
This commit is contained in:
parent
543eb09321
commit
07aa494432
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
|
||||
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');
|
||||
|
||||
import isArray from 'shared/isArray';
|
||||
|
|
@ -42,7 +42,7 @@ function assertYieldsWereCleared(root) {
|
|||
}
|
||||
|
||||
function createJSXElementForTestComparison(type, props) {
|
||||
if (__DEV__ && enableRefAsProp) {
|
||||
if (__DEV__) {
|
||||
const element = {
|
||||
$$typeof: REACT_ELEMENT_TYPE,
|
||||
type: type,
|
||||
|
|
|
|||
|
|
@ -44,7 +44,6 @@ import {
|
|||
disableStringRefs,
|
||||
enableBinaryFlight,
|
||||
enablePostpone,
|
||||
enableRefAsProp,
|
||||
enableFlightReadableStream,
|
||||
enableOwnerStacks,
|
||||
enableServerComponentLogs,
|
||||
|
|
@ -676,7 +675,7 @@ function createElement(
|
|||
| React$Element<any>
|
||||
| LazyComponent<React$Element<any>, SomeChunk<React$Element<any>>> {
|
||||
let element: any;
|
||||
if (__DEV__ && enableRefAsProp) {
|
||||
if (__DEV__) {
|
||||
// `ref` is non-enumerable in dev
|
||||
element = ({
|
||||
$$typeof: REACT_ELEMENT_TYPE,
|
||||
|
|
|
|||
|
|
@ -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', () => {
|
||||
const Root = ({children}) => React.createElement('div', null, children);
|
||||
const Component = () => React.createElement('div', null);
|
||||
|
|
|
|||
|
|
@ -197,223 +197,6 @@ describe('ReactFunctionComponent', () => {
|
|||
.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 () => {
|
||||
function Child() {
|
||||
return <div>{[<span />]}</div>;
|
||||
|
|
|
|||
18
packages/react-dom/src/__tests__/refs-test.js
vendored
18
packages/react-dom/src/__tests__/refs-test.js
vendored
|
|
@ -369,24 +369,6 @@ describe('ref swapping', () => {
|
|||
});
|
||||
}).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', () => {
|
||||
|
|
|
|||
|
|
@ -35,11 +35,7 @@ import {
|
|||
ConcurrentRoot,
|
||||
LegacyRoot,
|
||||
} from 'react-reconciler/constants';
|
||||
import {
|
||||
enableRefAsProp,
|
||||
disableLegacyMode,
|
||||
disableStringRefs,
|
||||
} from 'shared/ReactFeatureFlags';
|
||||
import {disableLegacyMode, disableStringRefs} from 'shared/ReactFeatureFlags';
|
||||
|
||||
import ReactSharedInternals from 'shared/ReactSharedInternals';
|
||||
import ReactVersion from 'shared/ReactVersion';
|
||||
|
|
@ -833,7 +829,7 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
|
|||
let currentEventPriority = DefaultEventPriority;
|
||||
|
||||
function createJSXElementForTestComparison(type, props) {
|
||||
if (__DEV__ && enableRefAsProp) {
|
||||
if (__DEV__) {
|
||||
const element = {
|
||||
type: type,
|
||||
$$typeof: REACT_ELEMENT_TYPE,
|
||||
|
|
|
|||
53
packages/react-reconciler/src/ReactChildFiber.js
vendored
53
packages/react-reconciler/src/ReactChildFiber.js
vendored
|
|
@ -45,7 +45,6 @@ import {
|
|||
} from './ReactWorkTags';
|
||||
import isArray from 'shared/isArray';
|
||||
import {
|
||||
enableRefAsProp,
|
||||
enableAsyncIterableChildren,
|
||||
disableLegacyMode,
|
||||
enableOwnerStacks,
|
||||
|
|
@ -239,21 +238,6 @@ function validateFragmentProps(
|
|||
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);
|
||||
}
|
||||
|
||||
function coerceRef(
|
||||
returnFiber: Fiber,
|
||||
current: Fiber | null,
|
||||
workInProgress: Fiber,
|
||||
element: ReactElement,
|
||||
): void {
|
||||
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
|
||||
function coerceRef(workInProgress: Fiber, element: ReactElement): void {
|
||||
// TODO: This is a temporary, intermediate step. Now that 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;
|
||||
// TODO: With enableRefAsProp now rolled out, we shouldn't use the `ref` field. We
|
||||
// should always read the ref from the prop.
|
||||
workInProgress.ref = ref;
|
||||
workInProgress.ref = refProp !== undefined ? refProp : null;
|
||||
}
|
||||
|
||||
function throwOnInvalidObjectType(returnFiber: Fiber, newChild: Object) {
|
||||
|
|
@ -569,7 +540,7 @@ function createChildReconciler(
|
|||
) {
|
||||
// Move based on index
|
||||
const existing = useFiber(current, element.props);
|
||||
coerceRef(returnFiber, current, existing, element);
|
||||
coerceRef(existing, element);
|
||||
existing.return = returnFiber;
|
||||
if (__DEV__) {
|
||||
existing._debugOwner = element._owner;
|
||||
|
|
@ -580,7 +551,7 @@ function createChildReconciler(
|
|||
}
|
||||
// Insert
|
||||
const created = createFiberFromElement(element, returnFiber.mode, lanes);
|
||||
coerceRef(returnFiber, current, created, element);
|
||||
coerceRef(created, element);
|
||||
created.return = returnFiber;
|
||||
if (__DEV__) {
|
||||
created._debugInfo = currentDebugInfo;
|
||||
|
|
@ -693,7 +664,7 @@ function createChildReconciler(
|
|||
returnFiber.mode,
|
||||
lanes,
|
||||
);
|
||||
coerceRef(returnFiber, null, created, newChild);
|
||||
coerceRef(created, newChild);
|
||||
created.return = returnFiber;
|
||||
if (__DEV__) {
|
||||
const prevDebugInfo = pushDebugInfo(newChild._debugInfo);
|
||||
|
|
@ -1684,7 +1655,7 @@ function createChildReconciler(
|
|||
) {
|
||||
deleteRemainingChildren(returnFiber, child.sibling);
|
||||
const existing = useFiber(child, element.props);
|
||||
coerceRef(returnFiber, child, existing, element);
|
||||
coerceRef(existing, element);
|
||||
existing.return = returnFiber;
|
||||
if (__DEV__) {
|
||||
existing._debugOwner = element._owner;
|
||||
|
|
@ -1722,7 +1693,7 @@ function createChildReconciler(
|
|||
return created;
|
||||
} else {
|
||||
const created = createFiberFromElement(element, returnFiber.mode, lanes);
|
||||
coerceRef(returnFiber, currentFirstChild, created, element);
|
||||
coerceRef(created, element);
|
||||
created.return = returnFiber;
|
||||
if (__DEV__) {
|
||||
created._debugInfo = currentDebugInfo;
|
||||
|
|
|
|||
|
|
@ -108,7 +108,6 @@ import {
|
|||
enableAsyncActions,
|
||||
enablePostpone,
|
||||
enableRenderableContext,
|
||||
enableRefAsProp,
|
||||
disableLegacyMode,
|
||||
disableDefaultPropsExceptForClasses,
|
||||
disableStringRefs,
|
||||
|
|
@ -125,10 +124,7 @@ import {
|
|||
REACT_MEMO_TYPE,
|
||||
getIteratorFn,
|
||||
} from 'shared/ReactSymbols';
|
||||
import {
|
||||
getCurrentFiberOwnerNameInDevOrNull,
|
||||
setCurrentFiber,
|
||||
} from './ReactCurrentFiber';
|
||||
import {setCurrentFiber} from './ReactCurrentFiber';
|
||||
import {
|
||||
resolveFunctionForHotReloading,
|
||||
resolveForwardRefForHotReloading,
|
||||
|
|
@ -319,7 +315,6 @@ let didWarnAboutBadClass;
|
|||
let didWarnAboutContextTypeOnFunctionComponent;
|
||||
let didWarnAboutContextTypes;
|
||||
let didWarnAboutGetDerivedStateOnFunctionComponent;
|
||||
let didWarnAboutFunctionRefs;
|
||||
export let didWarnAboutReassigningProps: boolean;
|
||||
let didWarnAboutRevealOrder;
|
||||
let didWarnAboutTailOptions;
|
||||
|
|
@ -330,7 +325,6 @@ if (__DEV__) {
|
|||
didWarnAboutContextTypeOnFunctionComponent = ({}: {[string]: boolean});
|
||||
didWarnAboutContextTypes = ({}: {[string]: boolean});
|
||||
didWarnAboutGetDerivedStateOnFunctionComponent = ({}: {[string]: boolean});
|
||||
didWarnAboutFunctionRefs = ({}: {[string]: boolean});
|
||||
didWarnAboutReassigningProps = false;
|
||||
didWarnAboutRevealOrder = ({}: {[empty]: boolean});
|
||||
didWarnAboutTailOptions = ({}: {[string]: boolean});
|
||||
|
|
@ -416,7 +410,7 @@ function updateForwardRef(
|
|||
const ref = workInProgress.ref;
|
||||
|
||||
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
|
||||
// the props object. This used to happen in the JSX runtime, but now we do
|
||||
// it here.
|
||||
|
|
@ -1954,25 +1948,6 @@ function validateFunctionComponentInDev(workInProgress: Fiber, Component: any) {
|
|||
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 (
|
||||
!disableDefaultPropsExceptForClasses &&
|
||||
|
|
|
|||
|
|
@ -23,7 +23,6 @@ import {
|
|||
enableDebugTracing,
|
||||
enableSchedulingProfiler,
|
||||
enableLazyContextPropagation,
|
||||
enableRefAsProp,
|
||||
disableDefaultPropsExceptForClasses,
|
||||
} from 'shared/ReactFeatureFlags';
|
||||
import ReactStrictModeWarnings from './ReactStrictModeWarnings';
|
||||
|
|
@ -1252,14 +1251,12 @@ export function resolveClassComponentProps(
|
|||
): Object {
|
||||
let newProps = baseProps;
|
||||
|
||||
if (enableRefAsProp) {
|
||||
// Remove ref from the props object, if it exists.
|
||||
if ('ref' in baseProps) {
|
||||
newProps = ({}: any);
|
||||
for (const propName in baseProps) {
|
||||
if (propName !== 'ref') {
|
||||
newProps[propName] = baseProps[propName];
|
||||
}
|
||||
// Remove ref from the props object, if it exists.
|
||||
if ('ref' in baseProps) {
|
||||
newProps = ({}: any);
|
||||
for (const propName in baseProps) {
|
||||
if (propName !== 'ref') {
|
||||
newProps[propName] = baseProps[propName];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -85,7 +85,6 @@ describe('ReactFiberRefs', () => {
|
|||
expect(ref2.current).not.toBe(null);
|
||||
});
|
||||
|
||||
// @gate enableRefAsProp
|
||||
// @gate !disableStringRefs
|
||||
it('string ref props are converted to function refs', async () => {
|
||||
let refProp;
|
||||
|
|
@ -105,7 +104,7 @@ describe('ReactFiberRefs', () => {
|
|||
const root = ReactNoop.createRoot();
|
||||
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.
|
||||
// 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
|
||||
|
|
|
|||
|
|
@ -1299,20 +1299,7 @@ describe('ReactIncrementalSideEffects', () => {
|
|||
|
||||
ReactNoop.render(<Foo show={true} />);
|
||||
|
||||
if (gate(flags => flags.enableRefAsProp)) {
|
||||
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 **)',
|
||||
);
|
||||
}
|
||||
await waitForAll([]);
|
||||
|
||||
expect(ops).toEqual([
|
||||
classInstance,
|
||||
|
|
|
|||
|
|
@ -1236,30 +1236,6 @@ describe('ReactLazy', () => {
|
|||
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 () => {
|
||||
let componentStackMessage;
|
||||
|
||||
|
|
|
|||
|
|
@ -44,43 +44,6 @@ describe('memo', () => {
|
|||
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`.
|
||||
// To make the tests work for both versions, we wrap the non-lazy version in
|
||||
// a lazy function component.
|
||||
|
|
|
|||
31
packages/react-server/src/ReactFizzServer.js
vendored
31
packages/react-server/src/ReactFizzServer.js
vendored
|
|
@ -160,7 +160,6 @@ import {
|
|||
enablePostpone,
|
||||
enableHalt,
|
||||
enableRenderableContext,
|
||||
enableRefAsProp,
|
||||
disableDefaultPropsExceptForClasses,
|
||||
enableAsyncIterableChildren,
|
||||
disableStringRefs,
|
||||
|
|
@ -1671,14 +1670,12 @@ export function resolveClassComponentProps(
|
|||
): Object {
|
||||
let newProps = baseProps;
|
||||
|
||||
if (enableRefAsProp) {
|
||||
// Remove ref from the props object, if it exists.
|
||||
if ('ref' in baseProps) {
|
||||
newProps = ({}: any);
|
||||
for (const propName in baseProps) {
|
||||
if (propName !== 'ref') {
|
||||
newProps[propName] = baseProps[propName];
|
||||
}
|
||||
// Remove ref from the props object, if it exists.
|
||||
if ('ref' in baseProps) {
|
||||
newProps = ({}: any);
|
||||
for (const propName in baseProps) {
|
||||
if (propName !== 'ref') {
|
||||
newProps[propName] = baseProps[propName];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1973,7 +1970,7 @@ function renderForwardRef(
|
|||
ref: any,
|
||||
): void {
|
||||
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
|
||||
// the props object. This used to happen in the JSX runtime, but now we do
|
||||
// it here.
|
||||
|
|
@ -2595,16 +2592,10 @@ function retryNode(request: Request, task: Task): void {
|
|||
const key = element.key;
|
||||
const props = element.props;
|
||||
|
||||
let ref;
|
||||
if (enableRefAsProp) {
|
||||
// TODO: This is a temporary, intermediate step. Once the feature
|
||||
// flag is removed, we should get the ref off the props object right
|
||||
// before using it.
|
||||
const refProp = props.ref;
|
||||
ref = refProp !== undefined ? refProp : null;
|
||||
} else {
|
||||
ref = element.ref;
|
||||
}
|
||||
// TODO: We should get the ref off the props object right before using
|
||||
// it.
|
||||
const refProp = props.ref;
|
||||
const ref = refProp !== undefined ? refProp : null;
|
||||
|
||||
const debugTask: null | ConsoleTask =
|
||||
__DEV__ && enableOwnerStacks ? task.debugTask : null;
|
||||
|
|
|
|||
15
packages/react-server/src/ReactFlightServer.js
vendored
15
packages/react-server/src/ReactFlightServer.js
vendored
|
|
@ -18,7 +18,6 @@ import {
|
|||
enablePostpone,
|
||||
enableHalt,
|
||||
enableTaint,
|
||||
enableRefAsProp,
|
||||
enableServerComponentLogs,
|
||||
enableOwnerStacks,
|
||||
} from 'shared/ReactFeatureFlags';
|
||||
|
|
@ -2512,16 +2511,10 @@ function renderModelDestructive(
|
|||
}
|
||||
|
||||
const props = element.props;
|
||||
let ref;
|
||||
if (enableRefAsProp) {
|
||||
// TODO: This is a temporary, intermediate step. Once the feature
|
||||
// flag is removed, we should get the ref off the props object right
|
||||
// before using it.
|
||||
const refProp = props.ref;
|
||||
ref = refProp !== undefined ? refProp : null;
|
||||
} else {
|
||||
ref = element.ref;
|
||||
}
|
||||
// TODO: We should get the ref off the props object right before using
|
||||
// it.
|
||||
const refProp = props.ref;
|
||||
const ref = refProp !== undefined ? refProp : null;
|
||||
|
||||
// Attempt to render the Server Component.
|
||||
|
||||
|
|
|
|||
|
|
@ -386,39 +386,6 @@ describe('ReactTestRenderer', () => {
|
|||
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 () => {
|
||||
const mockDivInstance = {appendChild: () => {}};
|
||||
const mockInputInstance = {focus: () => {}};
|
||||
|
|
@ -1226,11 +1193,9 @@ describe('ReactTestRenderer', () => {
|
|||
{
|
||||
instance: null,
|
||||
nodeType: 'host',
|
||||
props: gate(flags => flags.enableRefAsProp)
|
||||
? {
|
||||
ref: refFn,
|
||||
}
|
||||
: {},
|
||||
props: {
|
||||
ref: refFn,
|
||||
},
|
||||
rendered: [],
|
||||
type: 'span',
|
||||
},
|
||||
|
|
|
|||
|
|
@ -37,11 +37,7 @@ describe('ReactCreateElement', () => {
|
|||
const element = React.createElement(ComponentClass);
|
||||
expect(element.type).toBe(ComponentClass);
|
||||
expect(element.key).toBe(null);
|
||||
if (gate(flags => flags.enableRefAsProp)) {
|
||||
expect(element.ref).toBe(null);
|
||||
} else {
|
||||
expect(element.ref).toBe(null);
|
||||
}
|
||||
expect(element.ref).toBe(null);
|
||||
if (__DEV__) {
|
||||
expect(Object.isFrozen(element)).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', () => {
|
||||
const element = React.createElement('div');
|
||||
expect(element.type).toBe('div');
|
||||
expect(element.key).toBe(null);
|
||||
if (gate(flags => flags.enableRefAsProp)) {
|
||||
expect(element.ref).toBe(null);
|
||||
} else {
|
||||
expect(element.ref).toBe(null);
|
||||
}
|
||||
expect(element.ref).toBe(null);
|
||||
if (__DEV__) {
|
||||
expect(Object.isFrozen(element)).toBe(true);
|
||||
expect(Object.isFrozen(element.props)).toBe(true);
|
||||
|
|
@ -179,20 +141,13 @@ describe('ReactCreateElement', () => {
|
|||
foo: '56',
|
||||
});
|
||||
expect(element.type).toBe(ComponentClass);
|
||||
if (gate(flags => flags.enableRefAsProp)) {
|
||||
expect(() => expect(element.ref).toBe(ref)).toErrorDev(
|
||||
'Accessing element.ref was removed in React 19',
|
||||
{withoutStack: true},
|
||||
);
|
||||
const expectation = {foo: '56', ref};
|
||||
Object.freeze(expectation);
|
||||
expect(element.props).toEqual(expectation);
|
||||
} else {
|
||||
const expectation = {foo: '56'};
|
||||
Object.freeze(expectation);
|
||||
expect(element.props).toEqual(expectation);
|
||||
expect(element.ref).toBe(ref);
|
||||
}
|
||||
expect(() => expect(element.ref).toBe(ref)).toErrorDev(
|
||||
'Accessing element.ref was removed in React 19',
|
||||
{withoutStack: true},
|
||||
);
|
||||
const expectation = {foo: '56', ref};
|
||||
Object.freeze(expectation);
|
||||
expect(element.props).toEqual(expectation);
|
||||
});
|
||||
|
||||
it('extracts null key', () => {
|
||||
|
|
@ -218,11 +173,7 @@ describe('ReactCreateElement', () => {
|
|||
const element = React.createElement(ComponentClass, props);
|
||||
expect(element.type).toBe(ComponentClass);
|
||||
expect(element.key).toBe(null);
|
||||
if (gate(flags => flags.enableRefAsProp)) {
|
||||
expect(element.ref).toBe(null);
|
||||
} else {
|
||||
expect(element.ref).toBe(null);
|
||||
}
|
||||
expect(element.ref).toBe(null);
|
||||
if (__DEV__) {
|
||||
expect(Object.isFrozen(element)).toBe(true);
|
||||
expect(Object.isFrozen(element.props)).toBe(true);
|
||||
|
|
@ -234,11 +185,7 @@ describe('ReactCreateElement', () => {
|
|||
const elementA = React.createElement('div');
|
||||
const elementB = React.createElement('div', elementA.props);
|
||||
expect(elementB.key).toBe(null);
|
||||
if (gate(flags => flags.enableRefAsProp)) {
|
||||
expect(elementB.ref).toBe(null);
|
||||
} else {
|
||||
expect(elementB.ref).toBe(null);
|
||||
}
|
||||
expect(elementB.ref).toBe(null);
|
||||
});
|
||||
|
||||
it('coerces the key to a string', () => {
|
||||
|
|
@ -248,11 +195,7 @@ describe('ReactCreateElement', () => {
|
|||
});
|
||||
expect(element.type).toBe(ComponentClass);
|
||||
expect(element.key).toBe('12');
|
||||
if (gate(flags => flags.enableRefAsProp)) {
|
||||
expect(element.ref).toBe(null);
|
||||
} else {
|
||||
expect(element.ref).toBe(null);
|
||||
}
|
||||
expect(element.ref).toBe(null);
|
||||
if (__DEV__) {
|
||||
expect(Object.isFrozen(element)).toBe(true);
|
||||
expect(Object.isFrozen(element.props)).toBe(true);
|
||||
|
|
|
|||
|
|
@ -212,11 +212,7 @@ describe('ReactElementClone', () => {
|
|||
ref: this.xyzRef,
|
||||
});
|
||||
expect(clone.key).toBe('xyz');
|
||||
if (gate(flags => flags.enableRefAsProp)) {
|
||||
expect(clone.props.ref).toBe(this.xyzRef);
|
||||
} else {
|
||||
expect(clone.ref).toBe(this.xyzRef);
|
||||
}
|
||||
expect(clone.props.ref).toBe(this.xyzRef);
|
||||
return <div>{clone}</div>;
|
||||
}
|
||||
}
|
||||
|
|
@ -274,17 +270,13 @@ describe('ReactElementClone', () => {
|
|||
|
||||
const root = ReactDOMClient.createRoot(document.createElement('div'));
|
||||
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.parentRef.current.xyzRef.current.tagName).toBe('SPAN');
|
||||
} else if (
|
||||
gate(flags => !flags.enableRefAsProp && !flags.disableStringRefs)
|
||||
) {
|
||||
} else if (gate(flags => false)) {
|
||||
expect(component.childRef).toEqual({current: null});
|
||||
expect(component.parentRef.current.xyzRef.current.tagName).toBe('SPAN');
|
||||
} else if (
|
||||
gate(flags => flags.enableRefAsProp && !flags.disableStringRefs)
|
||||
) {
|
||||
} else if (gate(flags => !flags.disableStringRefs)) {
|
||||
expect(component.childRef).toEqual({current: null});
|
||||
expect(component.parentRef.current.xyzRef.current.tagName).toBe('SPAN');
|
||||
} else {
|
||||
|
|
@ -397,11 +389,7 @@ describe('ReactElementClone', () => {
|
|||
const elementA = React.createElement('div');
|
||||
const elementB = React.cloneElement(elementA, elementA.props);
|
||||
expect(elementB.key).toBe(null);
|
||||
if (gate(flags => flags.enableRefAsProp)) {
|
||||
expect(elementB.ref).toBe(null);
|
||||
} else {
|
||||
expect(elementB.ref).toBe(null);
|
||||
}
|
||||
expect(elementB.ref).toBe(null);
|
||||
});
|
||||
|
||||
it('should ignore undefined key and ref', () => {
|
||||
|
|
@ -418,21 +406,17 @@ describe('ReactElementClone', () => {
|
|||
const clone = React.cloneElement(element, props);
|
||||
expect(clone.type).toBe(ComponentClass);
|
||||
expect(clone.key).toBe('12');
|
||||
if (gate(flags => flags.enableRefAsProp && flags.disableStringRefs)) {
|
||||
if (gate(flags => flags.disableStringRefs)) {
|
||||
expect(clone.props.ref).toBe('34');
|
||||
expect(() => expect(clone.ref).toBe('34')).toErrorDev(
|
||||
'Accessing element.ref was removed in React 19',
|
||||
{withoutStack: true},
|
||||
);
|
||||
expect(clone.props).toEqual({foo: 'ef', ref: '34'});
|
||||
} else if (
|
||||
gate(flags => !flags.enableRefAsProp && !flags.disableStringRefs)
|
||||
) {
|
||||
} else if (gate(flags => false)) {
|
||||
expect(clone.ref).toBe(element.ref);
|
||||
expect(clone.props).toEqual({foo: 'ef'});
|
||||
} else if (
|
||||
gate(flags => flags.enableRefAsProp && !flags.disableStringRefs)
|
||||
) {
|
||||
} else if (gate(flags => !flags.disableStringRefs)) {
|
||||
expect(() => {
|
||||
expect(clone.ref).toBe(element.ref);
|
||||
}).toErrorDev('Accessing element.ref was removed in React 19', {
|
||||
|
|
@ -462,14 +446,8 @@ describe('ReactElementClone', () => {
|
|||
const clone = React.cloneElement(element, props);
|
||||
expect(clone.type).toBe(ComponentClass);
|
||||
expect(clone.key).toBe('null');
|
||||
if (gate(flags => flags.enableRefAsProp)) {
|
||||
expect(clone.ref).toBe(null);
|
||||
expect(clone.props).toEqual({foo: 'ef', ref: null});
|
||||
} else {
|
||||
expect(clone.ref).toBe(null);
|
||||
expect(clone.props).toEqual({foo: 'ef'});
|
||||
}
|
||||
|
||||
expect(clone.ref).toBe(null);
|
||||
expect(clone.props).toEqual({foo: 'ef', ref: null});
|
||||
if (__DEV__) {
|
||||
expect(Object.isFrozen(element)).toBe(true);
|
||||
expect(Object.isFrozen(element.props)).toBe(true);
|
||||
|
|
|
|||
|
|
@ -248,23 +248,13 @@ describe('ReactJSXElementValidator', () => {
|
|||
}
|
||||
}
|
||||
|
||||
if (gate(flags => flags.enableRefAsProp)) {
|
||||
await expect(async () => {
|
||||
const container = document.createElement('div');
|
||||
const root = ReactDOMClient.createRoot(container);
|
||||
await act(() => {
|
||||
root.render(<Foo />);
|
||||
});
|
||||
}).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`.');
|
||||
}
|
||||
await expect(async () => {
|
||||
const container = document.createElement('div');
|
||||
const root = ReactDOMClient.createRoot(container);
|
||||
await act(() => {
|
||||
root.render(<Foo />);
|
||||
});
|
||||
}).toErrorDev('Invalid prop `ref` supplied to `React.Fragment`.');
|
||||
});
|
||||
|
||||
it('does not warn for fragments of multiple elements without keys', async () => {
|
||||
|
|
|
|||
|
|
@ -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 () => {
|
||||
const container = document.createElement('div');
|
||||
|
||||
|
|
@ -377,7 +349,6 @@ describe('ReactJSXRuntime', () => {
|
|||
expect(didCall).toBe(false);
|
||||
});
|
||||
|
||||
// @gate enableRefAsProp
|
||||
it('does not clone props object if key and ref is not spread', async () => {
|
||||
const config = {
|
||||
foo: 'foo',
|
||||
|
|
|
|||
|
|
@ -55,11 +55,7 @@ describe('ReactJSXTransformIntegration', () => {
|
|||
const element = <Component />;
|
||||
expect(element.type).toBe(Component);
|
||||
expect(element.key).toBe(null);
|
||||
if (gate(flags => flags.enableRefAsProp)) {
|
||||
expect(element.ref).toBe(null);
|
||||
} else {
|
||||
expect(element.ref).toBe(null);
|
||||
}
|
||||
expect(element.ref).toBe(null);
|
||||
const expectation = {};
|
||||
Object.freeze(expectation);
|
||||
expect(element.props).toEqual(expectation);
|
||||
|
|
@ -69,11 +65,7 @@ describe('ReactJSXTransformIntegration', () => {
|
|||
const element = <div />;
|
||||
expect(element.type).toBe('div');
|
||||
expect(element.key).toBe(null);
|
||||
if (gate(flags => flags.enableRefAsProp)) {
|
||||
expect(element.ref).toBe(null);
|
||||
} else {
|
||||
expect(element.ref).toBe(null);
|
||||
}
|
||||
expect(element.ref).toBe(null);
|
||||
const expectation = {};
|
||||
Object.freeze(expectation);
|
||||
expect(element.props).toEqual(expectation);
|
||||
|
|
@ -84,11 +76,7 @@ describe('ReactJSXTransformIntegration', () => {
|
|||
const element = <TagName />;
|
||||
expect(element.type).toBe('div');
|
||||
expect(element.key).toBe(null);
|
||||
if (gate(flags => flags.enableRefAsProp)) {
|
||||
expect(element.ref).toBe(null);
|
||||
} else {
|
||||
expect(element.ref).toBe(null);
|
||||
}
|
||||
expect(element.ref).toBe(null);
|
||||
const expectation = {};
|
||||
Object.freeze(expectation);
|
||||
expect(element.props).toEqual(expectation);
|
||||
|
|
@ -124,31 +112,20 @@ describe('ReactJSXTransformIntegration', () => {
|
|||
const ref = React.createRef();
|
||||
const element = <Component ref={ref} foo="56" />;
|
||||
expect(element.type).toBe(Component);
|
||||
if (gate(flags => flags.enableRefAsProp)) {
|
||||
expect(() => expect(element.ref).toBe(ref)).toErrorDev(
|
||||
'Accessing element.ref was removed in React 19',
|
||||
{withoutStack: true},
|
||||
);
|
||||
const expectation = {foo: '56', ref};
|
||||
Object.freeze(expectation);
|
||||
expect(element.props).toEqual(expectation);
|
||||
} else {
|
||||
const expectation = {foo: '56'};
|
||||
Object.freeze(expectation);
|
||||
expect(element.props).toEqual(expectation);
|
||||
expect(element.ref).toBe(ref);
|
||||
}
|
||||
expect(() => expect(element.ref).toBe(ref)).toErrorDev(
|
||||
'Accessing element.ref was removed in React 19',
|
||||
{withoutStack: true},
|
||||
);
|
||||
const expectation = {foo: '56', ref};
|
||||
Object.freeze(expectation);
|
||||
expect(element.props).toEqual(expectation);
|
||||
});
|
||||
|
||||
it('coerces the key to a string', () => {
|
||||
const element = <Component key={12} foo="56" />;
|
||||
expect(element.type).toBe(Component);
|
||||
expect(element.key).toBe('12');
|
||||
if (gate(flags => flags.enableRefAsProp)) {
|
||||
expect(element.ref).toBe(null);
|
||||
} else {
|
||||
expect(element.ref).toBe(null);
|
||||
}
|
||||
expect(element.ref).toBe(null);
|
||||
const expectation = {foo: '56'};
|
||||
Object.freeze(expectation);
|
||||
expect(element.props).toEqual(expectation);
|
||||
|
|
|
|||
|
|
@ -20,7 +20,6 @@ import isValidElementType from 'shared/isValidElementType';
|
|||
import isArray from 'shared/isArray';
|
||||
import {describeUnknownElementTypeFrameInDEV} from 'shared/ReactComponentStackFrame';
|
||||
import {
|
||||
enableRefAsProp,
|
||||
disableStringRefs,
|
||||
disableDefaultPropsExceptForClasses,
|
||||
enableOwnerStacks,
|
||||
|
|
@ -72,7 +71,6 @@ function getOwner() {
|
|||
}
|
||||
|
||||
let specialPropKeyWarningShown;
|
||||
let specialPropRefWarningShown;
|
||||
let didWarnAboutStringRefs;
|
||||
let didWarnAboutElementRef;
|
||||
let didWarnAboutOldJSXRuntime;
|
||||
|
|
@ -82,7 +80,7 @@ if (__DEV__ || enableLogStringRefsProd) {
|
|||
didWarnAboutElementRef = {};
|
||||
}
|
||||
|
||||
const enableFastJSXWithoutStringRefs = enableRefAsProp && disableStringRefs;
|
||||
const enableFastJSXWithoutStringRefs = disableStringRefs;
|
||||
|
||||
function hasValidRef(config) {
|
||||
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() {
|
||||
if (__DEV__) {
|
||||
const componentName = getComponentNameFromType(this.type);
|
||||
|
|
@ -225,7 +199,6 @@ function elementRefGetterWithDeprecationWarning() {
|
|||
function ReactElement(
|
||||
type,
|
||||
key,
|
||||
_ref,
|
||||
self,
|
||||
source,
|
||||
owner,
|
||||
|
|
@ -233,24 +206,18 @@ function ReactElement(
|
|||
debugStack,
|
||||
debugTask,
|
||||
) {
|
||||
let ref;
|
||||
if (enableRefAsProp) {
|
||||
// When enableRefAsProp is on, ignore whatever was passed as the ref
|
||||
// argument and treat `props.ref` as the source of truth. The only thing we
|
||||
// use this for is `element.ref`, which will log a deprecation warning on
|
||||
// access. In the next release, we can remove `element.ref` as well as the
|
||||
// `ref` argument.
|
||||
const refProp = props.ref;
|
||||
// Ignore whatever was passed as the ref argument and treat `props.ref` as
|
||||
// the source of truth. The only thing we use this for is `element.ref`,
|
||||
// which will log a deprecation warning on 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
|
||||
// backwards compatibility.
|
||||
ref = refProp !== undefined ? refProp : null;
|
||||
} else {
|
||||
ref = _ref;
|
||||
}
|
||||
// An undefined `element.ref` is coerced to `null` for
|
||||
// backwards compatibility.
|
||||
const ref = refProp !== undefined ? refProp : null;
|
||||
|
||||
let element;
|
||||
if (__DEV__ && enableRefAsProp) {
|
||||
if (__DEV__) {
|
||||
// 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
|
||||
// trigger the warning.
|
||||
|
|
@ -380,7 +347,6 @@ function ReactElement(
|
|||
*/
|
||||
export function jsxProd(type, config, maybeKey) {
|
||||
let key = null;
|
||||
let ref = null;
|
||||
|
||||
// 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" />
|
||||
|
|
@ -402,19 +368,9 @@ export function jsxProd(type, config, maybeKey) {
|
|||
key = '' + config.key;
|
||||
}
|
||||
|
||||
if (hasValidRef(config)) {
|
||||
if (!enableRefAsProp) {
|
||||
ref = config.ref;
|
||||
if (!disableStringRefs) {
|
||||
ref = coerceStringRef(ref, getOwner(), type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let props;
|
||||
if (
|
||||
(enableFastJSXWithoutStringRefs ||
|
||||
(enableRefAsProp && !('ref' in config))) &&
|
||||
(enableFastJSXWithoutStringRefs || !('ref' in config)) &&
|
||||
!('key' in config)
|
||||
) {
|
||||
// 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 = {};
|
||||
for (const propName in config) {
|
||||
// Skip over reserved prop names
|
||||
if (propName !== 'key' && (enableRefAsProp || propName !== 'ref')) {
|
||||
if (enableRefAsProp && !disableStringRefs && propName === 'ref') {
|
||||
if (propName !== 'key') {
|
||||
if (!disableStringRefs && propName === 'ref') {
|
||||
props.ref = coerceStringRef(config[propName], getOwner(), type);
|
||||
} else {
|
||||
props[propName] = config[propName];
|
||||
|
|
@ -459,7 +415,6 @@ export function jsxProd(type, config, maybeKey) {
|
|||
return ReactElement(
|
||||
type,
|
||||
key,
|
||||
ref,
|
||||
undefined,
|
||||
undefined,
|
||||
getOwner(),
|
||||
|
|
@ -662,7 +617,6 @@ function jsxDEVImpl(
|
|||
}
|
||||
|
||||
let key = null;
|
||||
let ref = null;
|
||||
|
||||
// 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" />
|
||||
|
|
@ -684,22 +638,15 @@ function jsxDEVImpl(
|
|||
key = '' + config.key;
|
||||
}
|
||||
|
||||
if (hasValidRef(config)) {
|
||||
if (!enableRefAsProp) {
|
||||
ref = config.ref;
|
||||
if (!disableStringRefs) {
|
||||
ref = coerceStringRef(ref, getOwner(), type);
|
||||
}
|
||||
}
|
||||
if (!disableStringRefs) {
|
||||
if (!disableStringRefs) {
|
||||
if (hasValidRef(config)) {
|
||||
warnIfStringRefCannotBeAutoConverted(config, self);
|
||||
}
|
||||
}
|
||||
|
||||
let props;
|
||||
if (
|
||||
(enableFastJSXWithoutStringRefs ||
|
||||
(enableRefAsProp && !('ref' in config))) &&
|
||||
(enableFastJSXWithoutStringRefs || !('ref' in config)) &&
|
||||
!('key' in config)
|
||||
) {
|
||||
// If key was not spread in, we can reuse the original props object. This
|
||||
|
|
@ -719,8 +666,8 @@ function jsxDEVImpl(
|
|||
props = {};
|
||||
for (const propName in config) {
|
||||
// Skip over reserved prop names
|
||||
if (propName !== 'key' && (enableRefAsProp || propName !== 'ref')) {
|
||||
if (enableRefAsProp && !disableStringRefs && propName === 'ref') {
|
||||
if (propName !== 'key') {
|
||||
if (!disableStringRefs && propName === 'ref') {
|
||||
props.ref = coerceStringRef(config[propName], getOwner(), type);
|
||||
} else {
|
||||
props[propName] = config[propName];
|
||||
|
|
@ -741,23 +688,17 @@ function jsxDEVImpl(
|
|||
}
|
||||
}
|
||||
|
||||
if (key || (!enableRefAsProp && ref)) {
|
||||
if (key) {
|
||||
const displayName =
|
||||
typeof type === 'function'
|
||||
? type.displayName || type.name || 'Unknown'
|
||||
: type;
|
||||
if (key) {
|
||||
defineKeyPropWarningGetter(props, displayName);
|
||||
}
|
||||
if (!enableRefAsProp && ref) {
|
||||
defineRefPropWarningGetter(props, displayName);
|
||||
}
|
||||
defineKeyPropWarningGetter(props, displayName);
|
||||
}
|
||||
|
||||
return ReactElement(
|
||||
type,
|
||||
key,
|
||||
ref,
|
||||
self,
|
||||
source,
|
||||
getOwner(),
|
||||
|
|
@ -838,7 +779,6 @@ export function createElement(type, config, children) {
|
|||
const props = {};
|
||||
|
||||
let key = null;
|
||||
let ref = null;
|
||||
|
||||
if (config != null) {
|
||||
if (__DEV__) {
|
||||
|
|
@ -861,15 +801,8 @@ export function createElement(type, config, children) {
|
|||
}
|
||||
}
|
||||
|
||||
if (hasValidRef(config)) {
|
||||
if (!enableRefAsProp) {
|
||||
ref = config.ref;
|
||||
if (!disableStringRefs) {
|
||||
ref = coerceStringRef(ref, getOwner(), type);
|
||||
}
|
||||
}
|
||||
|
||||
if (__DEV__ && !disableStringRefs) {
|
||||
if (__DEV__ && !disableStringRefs) {
|
||||
if (hasValidRef(config)) {
|
||||
warnIfStringRefCannotBeAutoConverted(config, config.__self);
|
||||
}
|
||||
}
|
||||
|
|
@ -886,7 +819,6 @@ export function createElement(type, config, children) {
|
|||
hasOwnProperty.call(config, propName) &&
|
||||
// Skip over reserved prop names
|
||||
propName !== 'key' &&
|
||||
(enableRefAsProp || propName !== 'ref') &&
|
||||
// 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.
|
||||
// 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 !== '__source'
|
||||
) {
|
||||
if (enableRefAsProp && !disableStringRefs && propName === 'ref') {
|
||||
if (!disableStringRefs && propName === 'ref') {
|
||||
props.ref = coerceStringRef(config[propName], getOwner(), type);
|
||||
} else {
|
||||
props[propName] = config[propName];
|
||||
|
|
@ -931,24 +863,18 @@ export function createElement(type, config, children) {
|
|||
}
|
||||
}
|
||||
if (__DEV__) {
|
||||
if (key || (!enableRefAsProp && ref)) {
|
||||
if (key) {
|
||||
const displayName =
|
||||
typeof type === 'function'
|
||||
? type.displayName || type.name || 'Unknown'
|
||||
: type;
|
||||
if (key) {
|
||||
defineKeyPropWarningGetter(props, displayName);
|
||||
}
|
||||
if (!enableRefAsProp && ref) {
|
||||
defineRefPropWarningGetter(props, displayName);
|
||||
}
|
||||
defineKeyPropWarningGetter(props, displayName);
|
||||
}
|
||||
}
|
||||
|
||||
return ReactElement(
|
||||
type,
|
||||
key,
|
||||
ref,
|
||||
undefined,
|
||||
undefined,
|
||||
getOwner(),
|
||||
|
|
@ -962,9 +888,6 @@ export function cloneAndReplaceKey(oldElement, newKey) {
|
|||
const clonedElement = ReactElement(
|
||||
oldElement.type,
|
||||
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,
|
||||
!__DEV__ && disableStringRefs ? undefined : oldElement._owner,
|
||||
|
|
@ -997,7 +920,6 @@ export function cloneElement(element, config, children) {
|
|||
|
||||
// Reserved names are extracted
|
||||
let key = element.key;
|
||||
let ref = enableRefAsProp ? null : element.ref;
|
||||
|
||||
// Owner will be preserved, unless ref is overridden
|
||||
let owner = !__DEV__ && disableStringRefs ? undefined : element._owner;
|
||||
|
|
@ -1005,13 +927,6 @@ export function cloneElement(element, config, children) {
|
|||
if (config != null) {
|
||||
if (hasValidRef(config)) {
|
||||
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 (__DEV__) {
|
||||
|
|
@ -1034,7 +949,6 @@ export function cloneElement(element, config, children) {
|
|||
hasOwnProperty.call(config, propName) &&
|
||||
// Skip over reserved prop names
|
||||
propName !== 'key' &&
|
||||
(enableRefAsProp || propName !== 'ref') &&
|
||||
// ...and maybe these, too, though we currently rely on them for
|
||||
// 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
|
||||
|
|
@ -1046,7 +960,7 @@ export function cloneElement(element, config, children) {
|
|||
// Undefined `ref` is ignored by cloneElement. We treat it the same as
|
||||
// if the property were missing. This is mostly for
|
||||
// backwards compatibility.
|
||||
!(enableRefAsProp && propName === 'ref' && config.ref === undefined)
|
||||
!(propName === 'ref' && config.ref === undefined)
|
||||
) {
|
||||
if (
|
||||
!disableDefaultPropsExceptForClasses &&
|
||||
|
|
@ -1056,7 +970,7 @@ export function cloneElement(element, config, children) {
|
|||
// Resolve default props
|
||||
props[propName] = defaultProps[propName];
|
||||
} else {
|
||||
if (enableRefAsProp && !disableStringRefs && propName === 'ref') {
|
||||
if (!disableStringRefs && propName === 'ref') {
|
||||
props.ref = coerceStringRef(config[propName], owner, element.type);
|
||||
} else {
|
||||
props[propName] = config[propName];
|
||||
|
|
@ -1082,7 +996,6 @@ export function cloneElement(element, config, children) {
|
|||
const clonedElement = ReactElement(
|
||||
element.type,
|
||||
key,
|
||||
ref,
|
||||
undefined,
|
||||
undefined,
|
||||
owner,
|
||||
|
|
|
|||
|
|
@ -208,13 +208,8 @@ export const enableFilterEmptyStringAttributesDOM = true;
|
|||
// Disabled caching behavior of `react/cache` in client runtimes.
|
||||
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;
|
||||
|
||||
/**
|
||||
* If set to a function, the function will be called with the component name
|
||||
* and ref string.
|
||||
|
|
|
|||
|
|
@ -73,7 +73,6 @@ export const enableProfilerCommitHooks = __PROFILE__;
|
|||
export const enableProfilerNestedUpdatePhase = __PROFILE__;
|
||||
export const enableProfilerTimer = __PROFILE__;
|
||||
export const enableReactTestRendererWarning = false;
|
||||
export const enableRefAsProp = true;
|
||||
export const enableRenderableContext = true;
|
||||
export const enableRetryLaneExpiration = false;
|
||||
export const enableSchedulingProfiler = __PROFILE__;
|
||||
|
|
|
|||
|
|
@ -63,7 +63,6 @@ export const enableOwnerStacks = false;
|
|||
export const enablePersistedModeClonedFlag = false;
|
||||
export const enablePostpone = false;
|
||||
export const enableReactTestRendererWarning = false;
|
||||
export const enableRefAsProp = true;
|
||||
export const enableRenderableContext = true;
|
||||
export const enableRetryLaneExpiration = false;
|
||||
export const enableSchedulingProfiler = __PROFILE__;
|
||||
|
|
|
|||
|
|
@ -91,7 +91,6 @@ export const enableSiblingPrerendering = false;
|
|||
// We really need to get rid of this whole module. Any test renderer specific
|
||||
// flags should be handled by the Fiber config.
|
||||
// const __NEXT_MAJOR__ = __EXPERIMENTAL__;
|
||||
export const enableRefAsProp = true;
|
||||
export const disableStringRefs = true;
|
||||
export const disableLegacyMode = true;
|
||||
export const disableLegacyContext = true;
|
||||
|
|
|
|||
|
|
@ -56,7 +56,6 @@ export const enableProfilerCommitHooks = __PROFILE__;
|
|||
export const enableProfilerNestedUpdatePhase = __PROFILE__;
|
||||
export const enableProfilerTimer = __PROFILE__;
|
||||
export const enableReactTestRendererWarning = false;
|
||||
export const enableRefAsProp = true;
|
||||
export const enableRenderableContext = true;
|
||||
export const enableRetryLaneExpiration = false;
|
||||
export const enableSchedulingProfiler = __PROFILE__;
|
||||
|
|
|
|||
|
|
@ -83,7 +83,6 @@ export const disableClientCache = true;
|
|||
export const enableServerComponentLogs = true;
|
||||
export const enableInfiniteRenderLoopDetection = false;
|
||||
|
||||
export const enableRefAsProp = true;
|
||||
export const disableStringRefs = false;
|
||||
|
||||
export const enableReactTestRendererWarning = false;
|
||||
|
|
|
|||
|
|
@ -101,8 +101,6 @@ export const enableLegacyHidden = true;
|
|||
|
||||
export const enableComponentStackLocations = true;
|
||||
|
||||
export const enableRefAsProp = true;
|
||||
|
||||
export const disableTextareaChildren = __EXPERIMENTAL__;
|
||||
|
||||
export const consoleManagedByDevToolsDuringStrictMode = true;
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user