diff --git a/packages/react-debug-tools/src/__tests__/ReactHooksInspectionIntegration-test.js b/packages/react-debug-tools/src/__tests__/ReactHooksInspectionIntegration-test.js
index 0d87dbd81aaa6..95ac5e066e06b 100644
--- a/packages/react-debug-tools/src/__tests__/ReactHooksInspectionIntegration-test.js
+++ b/packages/react-debug-tools/src/__tests__/ReactHooksInspectionIntegration-test.js
@@ -2293,6 +2293,7 @@ describe('ReactHooksInspectionIntegration', () => {
});
});
+ // @gate !disableDefaultPropsExceptForClasses
it('should support defaultProps and lazy', async () => {
const Suspense = React.Suspense;
diff --git a/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js b/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js
index dab01e4bd9779..f44605571f9f0 100644
--- a/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js
+++ b/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js
@@ -405,18 +405,6 @@ describe('ReactDOMFizzServer', () => {
}
it('should asynchronously load a lazy component', async () => {
- const originalConsoleError = console.error;
- const mockError = jest.fn();
- console.error = (...args) => {
- if (args.length > 1) {
- if (typeof args[1] === 'object') {
- mockError(args[0].split('\n')[0]);
- return;
- }
- }
- mockError(...args.map(normalizeCodeLocInfo));
- };
-
let resolveA;
const LazyA = React.lazy(() => {
return new Promise(r => {
@@ -431,74 +419,59 @@ describe('ReactDOMFizzServer', () => {
});
});
- function TextWithPunctuation({text, punctuation}) {
- return ;
+ class TextWithPunctuation extends React.Component {
+ render() {
+ return ;
+ }
}
+
// This tests that default props of the inner element is resolved.
TextWithPunctuation.defaultProps = {
punctuation: '!',
};
- try {
- await act(() => {
- const {pipe} = renderToPipeableStream(
-
-
- }>
-
-
-
-
- }>
-
-
-
-
,
- );
- pipe(writable);
- });
-
- expect(getVisibleChildren(container)).toEqual(
-
-
Loading...
-
Loading...
-
,
- );
- await act(() => {
- resolveA({default: Text});
- });
- expect(getVisibleChildren(container)).toEqual(
- ,
- );
- await act(() => {
- resolveB({default: TextWithPunctuation});
- });
- expect(getVisibleChildren(container)).toEqual(
+ await act(() => {
+ const {pipe} = renderToPipeableStream(
-
Hello
-
world!
+
+ }>
+
+
+
+
+ }>
+
+
+
,
);
+ pipe(writable);
+ });
- if (__DEV__) {
- expect(mockError).toHaveBeenCalledWith(
- 'Warning: %s: Support for defaultProps will be removed from function components in a future major release. Use JavaScript default parameters instead.%s',
- 'TextWithPunctuation',
- '\n in TextWithPunctuation (at **)\n' +
- ' in Lazy (at **)\n' +
- ' in Suspense (at **)\n' +
- ' in div (at **)\n' +
- ' in div (at **)',
- );
- } else {
- expect(mockError).not.toHaveBeenCalled();
- }
- } finally {
- console.error = originalConsoleError;
- }
+ expect(getVisibleChildren(container)).toEqual(
+
+
Loading...
+
Loading...
+
,
+ );
+ await act(() => {
+ resolveA({default: Text});
+ });
+ expect(getVisibleChildren(container)).toEqual(
+ ,
+ );
+ await act(() => {
+ resolveB({default: TextWithPunctuation});
+ });
+ expect(getVisibleChildren(container)).toEqual(
+ ,
+ );
});
it('#23331: does not warn about hydration mismatches if something suspended in an earlier sibling', async () => {
diff --git a/packages/react-dom/src/__tests__/ReactDeprecationWarnings-test.js b/packages/react-dom/src/__tests__/ReactDeprecationWarnings-test.js
index f2ec54f4ea018..39a068e1c7ed8 100644
--- a/packages/react-dom/src/__tests__/ReactDeprecationWarnings-test.js
+++ b/packages/react-dom/src/__tests__/ReactDeprecationWarnings-test.js
@@ -26,6 +26,7 @@ describe('ReactDeprecationWarnings', () => {
}
});
+ // @gate !disableDefaultPropsExceptForClasses || !__DEV__
it('should warn when given defaultProps', async () => {
function FunctionalComponent(props) {
return null;
@@ -43,6 +44,7 @@ describe('ReactDeprecationWarnings', () => {
);
});
+ // @gate !disableDefaultPropsExceptForClasses || !__DEV__
it('should warn when given defaultProps on a memoized function', async () => {
const MemoComponent = React.memo(function FunctionalComponent(props) {
return null;
diff --git a/packages/react-dom/src/__tests__/ReactFunctionComponent-test.js b/packages/react-dom/src/__tests__/ReactFunctionComponent-test.js
index b1409c580c709..d353f6f8c6ee6 100644
--- a/packages/react-dom/src/__tests__/ReactFunctionComponent-test.js
+++ b/packages/react-dom/src/__tests__/ReactFunctionComponent-test.js
@@ -433,6 +433,7 @@ describe('ReactFunctionComponent', () => {
);
});
+ // @gate !disableDefaultPropsExceptForClasses
it('should support default props', async () => {
function Child(props) {
return {props.test}
;
@@ -446,6 +447,7 @@ describe('ReactFunctionComponent', () => {
await act(() => {
root.render();
});
+ expect(container.textContent).toBe('2');
}).toErrorDev([
'Warning: Child: Support for defaultProps will be removed from function components in a future major release. Use JavaScript default parameters instead.',
]);
diff --git a/packages/react-reconciler/src/ReactFiberBeginWork.js b/packages/react-reconciler/src/ReactFiberBeginWork.js
index 93740b161c732..6684d5595cc6f 100644
--- a/packages/react-reconciler/src/ReactFiberBeginWork.js
+++ b/packages/react-reconciler/src/ReactFiberBeginWork.js
@@ -109,6 +109,7 @@ import {
enableRenderableContext,
enableRefAsProp,
disableLegacyMode,
+ disableDefaultPropsExceptForClasses,
} from 'shared/ReactFeatureFlags';
import isArray from 'shared/isArray';
import shallowEqual from 'shared/shallowEqual';
@@ -247,7 +248,7 @@ import {
updateClassInstance,
resolveClassComponentProps,
} from './ReactFiberClassComponent';
-import {resolveDefaultProps} from './ReactFiberLazyComponent';
+import {resolveDefaultPropsOnNonClassComponent} from './ReactFiberLazyComponent';
import {
createFiberFromTypeAndProps,
createFiberFromFragment,
@@ -487,7 +488,8 @@ function updateMemoComponent(
isSimpleFunctionComponent(type) &&
Component.compare === null &&
// SimpleMemoComponent codepath doesn't resolve outer props either.
- Component.defaultProps === undefined
+ (disableDefaultPropsExceptForClasses ||
+ Component.defaultProps === undefined)
) {
let resolvedType = type;
if (__DEV__) {
@@ -509,16 +511,18 @@ function updateMemoComponent(
renderLanes,
);
}
- if (__DEV__) {
- if (Component.defaultProps !== undefined) {
- const componentName = getComponentNameFromType(type) || 'Unknown';
- if (!didWarnAboutDefaultPropsOnFunctionComponent[componentName]) {
- console.error(
- '%s: Support for defaultProps will be removed from memo components ' +
- 'in a future major release. Use JavaScript default parameters instead.',
- componentName,
- );
- didWarnAboutDefaultPropsOnFunctionComponent[componentName] = true;
+ if (!disableDefaultPropsExceptForClasses) {
+ if (__DEV__) {
+ if (Component.defaultProps !== undefined) {
+ const componentName = getComponentNameFromType(type) || 'Unknown';
+ if (!didWarnAboutDefaultPropsOnFunctionComponent[componentName]) {
+ console.error(
+ '%s: Support for defaultProps will be removed from memo components ' +
+ 'in a future major release. Use JavaScript default parameters instead.',
+ componentName,
+ );
+ didWarnAboutDefaultPropsOnFunctionComponent[componentName] = true;
+ }
}
}
}
@@ -1767,7 +1771,9 @@ function mountLazyComponent(
renderLanes,
);
} else {
- const resolvedProps = resolveDefaultProps(Component, props);
+ const resolvedProps = disableDefaultPropsExceptForClasses
+ ? props
+ : resolveDefaultPropsOnNonClassComponent(Component, props);
workInProgress.tag = FunctionComponent;
if (__DEV__) {
validateFunctionComponentInDev(workInProgress, Component);
@@ -1785,7 +1791,9 @@ function mountLazyComponent(
} else if (Component !== undefined && Component !== null) {
const $$typeof = Component.$$typeof;
if ($$typeof === REACT_FORWARD_REF_TYPE) {
- const resolvedProps = resolveDefaultProps(Component, props);
+ const resolvedProps = disableDefaultPropsExceptForClasses
+ ? props
+ : resolveDefaultPropsOnNonClassComponent(Component, props);
workInProgress.tag = ForwardRef;
if (__DEV__) {
workInProgress.type = Component =
@@ -1799,13 +1807,20 @@ function mountLazyComponent(
renderLanes,
);
} else if ($$typeof === REACT_MEMO_TYPE) {
- const resolvedProps = resolveDefaultProps(Component, props);
+ const resolvedProps = disableDefaultPropsExceptForClasses
+ ? props
+ : resolveDefaultPropsOnNonClassComponent(Component, props);
workInProgress.tag = MemoComponent;
return updateMemoComponent(
null,
workInProgress,
Component,
- resolveDefaultProps(Component.type, resolvedProps), // The inner type can have defaults too
+ disableDefaultPropsExceptForClasses
+ ? resolvedProps
+ : resolveDefaultPropsOnNonClassComponent(
+ Component.type,
+ resolvedProps,
+ ), // The inner type can have defaults too
renderLanes,
);
}
@@ -1901,7 +1916,10 @@ function validateFunctionComponentInDev(workInProgress: Fiber, Component: any) {
}
}
- if (Component.defaultProps !== undefined) {
+ if (
+ !disableDefaultPropsExceptForClasses &&
+ Component.defaultProps !== undefined
+ ) {
const componentName = getComponentNameFromType(Component) || 'Unknown';
if (!didWarnAboutDefaultPropsOnFunctionComponent[componentName]) {
@@ -3898,9 +3916,10 @@ function beginWork(
const Component = workInProgress.type;
const unresolvedProps = workInProgress.pendingProps;
const resolvedProps =
+ disableDefaultPropsExceptForClasses ||
workInProgress.elementType === Component
? unresolvedProps
- : resolveDefaultProps(Component, unresolvedProps);
+ : resolveDefaultPropsOnNonClassComponent(Component, unresolvedProps);
return updateFunctionComponent(
current,
workInProgress,
@@ -3949,9 +3968,10 @@ function beginWork(
const type = workInProgress.type;
const unresolvedProps = workInProgress.pendingProps;
const resolvedProps =
+ disableDefaultPropsExceptForClasses ||
workInProgress.elementType === type
? unresolvedProps
- : resolveDefaultProps(type, unresolvedProps);
+ : resolveDefaultPropsOnNonClassComponent(type, unresolvedProps);
return updateForwardRef(
current,
workInProgress,
@@ -3974,8 +3994,12 @@ function beginWork(
const type = workInProgress.type;
const unresolvedProps = workInProgress.pendingProps;
// Resolve outer props first, then resolve inner props.
- let resolvedProps = resolveDefaultProps(type, unresolvedProps);
- resolvedProps = resolveDefaultProps(type.type, resolvedProps);
+ let resolvedProps = disableDefaultPropsExceptForClasses
+ ? unresolvedProps
+ : resolveDefaultPropsOnNonClassComponent(type, unresolvedProps);
+ resolvedProps = disableDefaultPropsExceptForClasses
+ ? resolvedProps
+ : resolveDefaultPropsOnNonClassComponent(type.type, resolvedProps);
return updateMemoComponent(
current,
workInProgress,
diff --git a/packages/react-reconciler/src/ReactFiberClassComponent.js b/packages/react-reconciler/src/ReactFiberClassComponent.js
index 697be25eee678..e464670bfedb4 100644
--- a/packages/react-reconciler/src/ReactFiberClassComponent.js
+++ b/packages/react-reconciler/src/ReactFiberClassComponent.js
@@ -24,6 +24,7 @@ import {
enableSchedulingProfiler,
enableLazyContextPropagation,
enableRefAsProp,
+ disableDefaultPropsExceptForClasses,
} from 'shared/ReactFeatureFlags';
import ReactStrictModeWarnings from './ReactStrictModeWarnings';
import {isMounted} from './ReactFiberTreeReflection';
@@ -1252,7 +1253,12 @@ export function resolveClassComponentProps(
// Resolve default props. Taken from old JSX runtime, where this used to live.
const defaultProps = Component.defaultProps;
- if (defaultProps && !alreadyResolvedDefaultProps) {
+ if (
+ defaultProps &&
+ // If disableDefaultPropsExceptForClasses is true, we always resolve
+ // default props here in the reconciler, rather than in the JSX runtime.
+ (disableDefaultPropsExceptForClasses || !alreadyResolvedDefaultProps)
+ ) {
newProps = assign({}, newProps, baseProps);
for (const propName in defaultProps) {
if (newProps[propName] === undefined) {
diff --git a/packages/react-reconciler/src/ReactFiberLazyComponent.js b/packages/react-reconciler/src/ReactFiberLazyComponent.js
index b6c28f0ed5cce..36e16c2b40800 100644
--- a/packages/react-reconciler/src/ReactFiberLazyComponent.js
+++ b/packages/react-reconciler/src/ReactFiberLazyComponent.js
@@ -8,12 +8,17 @@
*/
import assign from 'shared/assign';
+import {disableDefaultPropsExceptForClasses} from 'shared/ReactFeatureFlags';
-export function resolveDefaultProps(Component: any, baseProps: Object): Object {
- // TODO: Remove support for default props for everything except class
- // components, including setting default props on a lazy wrapper around a
- // class type.
-
+export function resolveDefaultPropsOnNonClassComponent(
+ Component: any,
+ baseProps: Object,
+): Object {
+ if (disableDefaultPropsExceptForClasses) {
+ // Support for defaultProps is removed in React 19 for all types
+ // except classes.
+ return baseProps;
+ }
if (Component && Component.defaultProps) {
// Resolve default props. Taken from ReactElement
const props = assign({}, baseProps);
diff --git a/packages/react-reconciler/src/ReactFiberWorkLoop.js b/packages/react-reconciler/src/ReactFiberWorkLoop.js
index 41d9bcdeaf8bf..e4b534fa9c33f 100644
--- a/packages/react-reconciler/src/ReactFiberWorkLoop.js
+++ b/packages/react-reconciler/src/ReactFiberWorkLoop.js
@@ -40,6 +40,7 @@ import {
alwaysThrottleRetries,
enableInfiniteRenderLoopDetection,
disableLegacyMode,
+ disableDefaultPropsExceptForClasses,
} from 'shared/ReactFeatureFlags';
import ReactSharedInternals from 'shared/ReactSharedInternals';
import is from 'shared/objectIs';
@@ -264,7 +265,7 @@ import {
getSuspenseHandler,
getShellBoundary,
} from './ReactFiberSuspenseContext';
-import {resolveDefaultProps} from './ReactFiberLazyComponent';
+import {resolveDefaultPropsOnNonClassComponent} from './ReactFiberLazyComponent';
import {resetChildReconcilerOnUnwind} from './ReactChildFiber';
import {
ensureRootIsScheduled,
@@ -2411,9 +2412,10 @@ function replaySuspendedUnitOfWork(unitOfWork: Fiber): void {
const Component = unitOfWork.type;
const unresolvedProps = unitOfWork.pendingProps;
const resolvedProps =
+ disableDefaultPropsExceptForClasses ||
unitOfWork.elementType === Component
? unresolvedProps
- : resolveDefaultProps(Component, unresolvedProps);
+ : resolveDefaultPropsOnNonClassComponent(Component, unresolvedProps);
let context: any;
if (!disableLegacyContext) {
const unmaskedContext = getUnmaskedContext(unitOfWork, Component, true);
@@ -2437,9 +2439,10 @@ function replaySuspendedUnitOfWork(unitOfWork: Fiber): void {
const Component = unitOfWork.type.render;
const unresolvedProps = unitOfWork.pendingProps;
const resolvedProps =
+ disableDefaultPropsExceptForClasses ||
unitOfWork.elementType === Component
? unresolvedProps
- : resolveDefaultProps(Component, unresolvedProps);
+ : resolveDefaultPropsOnNonClassComponent(Component, unresolvedProps);
next = replayFunctionComponent(
current,
diff --git a/packages/react-reconciler/src/__tests__/ReactLazy-test.internal.js b/packages/react-reconciler/src/__tests__/ReactLazy-test.internal.js
index 4091c064aac8b..03b9d62732b56 100644
--- a/packages/react-reconciler/src/__tests__/ReactLazy-test.internal.js
+++ b/packages/react-reconciler/src/__tests__/ReactLazy-test.internal.js
@@ -328,8 +328,10 @@ describe('ReactLazy', () => {
});
it('resolves defaultProps, on mount and update', async () => {
- function T(props) {
- return ;
+ class T extends React.Component {
+ render() {
+ return ;
+ }
}
T.defaultProps = {text: 'Hi'};
const LazyText = lazy(() => fakeImport(T));
@@ -346,14 +348,8 @@ describe('ReactLazy', () => {
await waitForAll(['Loading...']);
expect(root).not.toMatchRenderedOutput('Hi');
- await expect(async () => {
- await act(() => resolveFakeImport(T));
- assertLog(['Hi']);
- }).toErrorDev(
- 'Warning: T: Support for defaultProps ' +
- 'will be removed from function components in a future major ' +
- 'release. Use JavaScript default parameters instead.',
- );
+ await act(() => resolveFakeImport(T));
+ assertLog(['Hi']);
expect(root).toMatchRenderedOutput('Hi');
@@ -368,14 +364,16 @@ describe('ReactLazy', () => {
});
it('resolves defaultProps without breaking memoization', async () => {
- function LazyImpl(props) {
- Scheduler.log('Lazy');
- return (
- <>
-
- {props.children}
- >
- );
+ class LazyImpl extends React.Component {
+ render() {
+ Scheduler.log('Lazy');
+ return (
+ <>
+
+ {this.props.children}
+ >
+ );
+ }
}
LazyImpl.defaultProps = {siblingText: 'Sibling'};
const Lazy = lazy(() => fakeImport(LazyImpl));
@@ -402,14 +400,8 @@ describe('ReactLazy', () => {
await waitForAll(['Loading...']);
expect(root).not.toMatchRenderedOutput('SiblingA');
- await expect(async () => {
- await act(() => resolveFakeImport(LazyImpl));
- assertLog(['Lazy', 'Sibling', 'A']);
- }).toErrorDev(
- 'Warning: LazyImpl: Support for defaultProps ' +
- 'will be removed from function components in a future major ' +
- 'release. Use JavaScript default parameters instead.',
- );
+ await act(() => resolveFakeImport(LazyImpl));
+ assertLog(['Lazy', 'Sibling', 'A']);
expect(root).toMatchRenderedOutput('SiblingA');
@@ -680,6 +672,7 @@ describe('ReactLazy', () => {
expect(root).toMatchRenderedOutput('A3');
});
+ // @gate !disableDefaultPropsExceptForClasses
it('resolves defaultProps on the outer wrapper but warns', async () => {
function T(props) {
Scheduler.log(props.inner + ' ' + props.outer);
@@ -837,6 +830,7 @@ describe('ReactLazy', () => {
expect(root).toMatchRenderedOutput('0');
}
+ // @gate !disableDefaultPropsExceptForClasses
it('resolves props for function component with defaultProps', async () => {
function Add(props) {
expect(props.innerWithDefault).toBe(42);
@@ -877,6 +871,7 @@ describe('ReactLazy', () => {
await verifyResolvesProps(Add);
});
+ // @gate !disableDefaultPropsExceptForClasses
it('resolves props for forwardRef component with defaultProps', async () => {
const Add = React.forwardRef((props, ref) => {
expect(props.innerWithDefault).toBe(42);
@@ -897,6 +892,7 @@ describe('ReactLazy', () => {
await verifyResolvesProps(Add);
});
+ // @gate !disableDefaultPropsExceptForClasses
it('resolves props for outer memo component with defaultProps', async () => {
let Add = props => {
expect(props.innerWithDefault).toBe(42);
@@ -917,6 +913,7 @@ describe('ReactLazy', () => {
await verifyResolvesProps(Add);
});
+ // @gate !disableDefaultPropsExceptForClasses
it('resolves props for inner memo component with defaultProps', async () => {
const Add = props => {
expect(props.innerWithDefault).toBe(42);
@@ -937,6 +934,7 @@ describe('ReactLazy', () => {
await verifyResolvesProps(React.memo(Add));
});
+ // @gate !disableDefaultPropsExceptForClasses
it('uses outer resolved props on memo', async () => {
let T = props => {
return ;
@@ -1052,6 +1050,7 @@ describe('ReactLazy', () => {
});
// Regression test for #14310
+ // @gate !disableDefaultPropsExceptForClasses
it('supports defaultProps defined on the memo() return value', async () => {
const Add = React.memo(props => {
return props.inner + props.outer;
@@ -1134,6 +1133,7 @@ describe('ReactLazy', () => {
expect(root).toMatchRenderedOutput('3');
});
+ // @gate !disableDefaultPropsExceptForClasses
it('merges defaultProps in the correct order', async () => {
let Add = React.memo(props => {
return props.inner + props.outer;
diff --git a/packages/react-reconciler/src/__tests__/ReactMemo-test.js b/packages/react-reconciler/src/__tests__/ReactMemo-test.js
index a70d5aa53dd51..e8d8ef384aa44 100644
--- a/packages/react-reconciler/src/__tests__/ReactMemo-test.js
+++ b/packages/react-reconciler/src/__tests__/ReactMemo-test.js
@@ -65,19 +65,17 @@ describe('memo', () => {
// @gate !enableRefAsProp || !__DEV__
it('warns when giving a ref (complex)', async () => {
- // defaultProps means this won't use SimpleMemoComponent (as of this writing)
- // SimpleMemoComponent is unobservable tho, so we can't check :)
function App() {
return null;
}
- App.defaultProps = {};
- App = React.memo(App);
+ // 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 {}} />;
}
ReactNoop.render();
await expect(async () => await waitForAll([])).toErrorDev([
- 'App: Support for defaultProps will be removed from function components in a future major release. Use JavaScript default parameters instead.',
'Warning: Function components cannot be given refs. Attempts to access ' +
'this ref will fail.',
]);
@@ -409,6 +407,7 @@ describe('memo', () => {
expect(ReactNoop).toMatchRenderedOutput();
});
+ // @gate !disableDefaultPropsExceptForClasses
it('supports defaultProps defined on the memo() return value', async () => {
function Counter({a, b, c, d, e}) {
return ;
@@ -483,6 +482,7 @@ describe('memo', () => {
);
});
+ // @gate !disableDefaultPropsExceptForClasses
it('handles nested defaultProps declarations', async () => {
function Inner(props) {
return props.inner + props.middle + props.outer;
diff --git a/packages/react-server/src/ReactFizzServer.js b/packages/react-server/src/ReactFizzServer.js
index 00f00522f313f..327311d74fa31 100644
--- a/packages/react-server/src/ReactFizzServer.js
+++ b/packages/react-server/src/ReactFizzServer.js
@@ -143,6 +143,7 @@ import {
enablePostpone,
enableRenderableContext,
enableRefAsProp,
+ disableDefaultPropsExceptForClasses,
} from 'shared/ReactFeatureFlags';
import assign from 'shared/assign';
@@ -1396,12 +1397,23 @@ export function resolveClassComponentProps(
): Object {
let newProps = baseProps;
- // TODO: This is where defaultProps should be resolved, too.
+ // Resolve default props. Taken from old JSX runtime, where this used to live.
+ const defaultProps = Component.defaultProps;
+ if (defaultProps && disableDefaultPropsExceptForClasses) {
+ newProps = assign({}, newProps, baseProps);
+ for (const propName in defaultProps) {
+ if (newProps[propName] === undefined) {
+ newProps[propName] = defaultProps[propName];
+ }
+ }
+ }
if (enableRefAsProp) {
// Remove ref from the props object, if it exists.
if ('ref' in newProps) {
- newProps = assign({}, newProps);
+ if (newProps === baseProps) {
+ newProps = assign({}, newProps);
+ }
delete newProps.ref;
}
}
@@ -1587,7 +1599,10 @@ function validateFunctionComponentInDev(Component: any): void {
}
}
- if (Component.defaultProps !== undefined) {
+ if (
+ !disableDefaultPropsExceptForClasses &&
+ Component.defaultProps !== undefined
+ ) {
const componentName = getComponentNameFromType(Component) || 'Unknown';
if (!didWarnAboutDefaultPropsOnFunctionComponent[componentName]) {
@@ -1629,7 +1644,15 @@ function validateFunctionComponentInDev(Component: any): void {
}
}
-function resolveDefaultProps(Component: any, baseProps: Object): Object {
+function resolveDefaultPropsOnNonClassComponent(
+ Component: any,
+ baseProps: Object,
+): Object {
+ if (disableDefaultPropsExceptForClasses) {
+ // Support for defaultProps is removed in React 19 for all types
+ // except classes.
+ return baseProps;
+ }
if (Component && Component.defaultProps) {
// Resolve default props. Taken from ReactElement
const props = assign({}, baseProps);
@@ -1705,7 +1728,10 @@ function renderMemo(
ref: any,
): void {
const innerType = type.type;
- const resolvedProps = resolveDefaultProps(innerType, props);
+ const resolvedProps = resolveDefaultPropsOnNonClassComponent(
+ innerType,
+ props,
+ );
renderElement(request, task, keyPath, innerType, resolvedProps, ref);
}
@@ -1779,7 +1805,10 @@ function renderLazyComponent(
const payload = lazyComponent._payload;
const init = lazyComponent._init;
const Component = init(payload);
- const resolvedProps = resolveDefaultProps(Component, props);
+ const resolvedProps = resolveDefaultPropsOnNonClassComponent(
+ Component,
+ props,
+ );
renderElement(request, task, keyPath, Component, resolvedProps, ref);
task.componentStack = previousComponentStack;
}
diff --git a/packages/react/src/ReactLazy.js b/packages/react/src/ReactLazy.js
index 5b5ef2b8caac7..e1b222ad8b222 100644
--- a/packages/react/src/ReactLazy.js
+++ b/packages/react/src/ReactLazy.js
@@ -10,6 +10,7 @@
import type {Wakeable, Thenable, ReactDebugInfo} from 'shared/ReactTypes';
import {REACT_LAZY_TYPE} from 'shared/ReactSymbols';
+import {disableDefaultPropsExceptForClasses} from 'shared/ReactFeatureFlags';
const Uninitialized = -1;
const Pending = 0;
@@ -134,53 +135,34 @@ export function lazy(
_init: lazyInitializer,
};
- if (__DEV__) {
- // In production, this would just set it on the object.
- let defaultProps;
- let propTypes;
- // $FlowFixMe[prop-missing]
- Object.defineProperties(lazyType, {
- defaultProps: {
- configurable: true,
- get() {
- return defaultProps;
- },
- // $FlowFixMe[missing-local-annot]
- set(newDefaultProps) {
- console.error(
- 'It is not supported to assign `defaultProps` to ' +
- 'a lazy component import. Either specify them where the component ' +
- 'is defined, or create a wrapping component around it.',
- );
- defaultProps = newDefaultProps;
- // Match production behavior more closely:
- // $FlowFixMe[prop-missing]
- Object.defineProperty(lazyType, 'defaultProps', {
- enumerable: true,
- });
- },
- },
- propTypes: {
- configurable: true,
- get() {
- return propTypes;
- },
- // $FlowFixMe[missing-local-annot]
- set(newPropTypes) {
- console.error(
- 'It is not supported to assign `propTypes` to ' +
- 'a lazy component import. Either specify them where the component ' +
- 'is defined, or create a wrapping component around it.',
- );
- propTypes = newPropTypes;
- // Match production behavior more closely:
- // $FlowFixMe[prop-missing]
- Object.defineProperty(lazyType, 'propTypes', {
- enumerable: true,
- });
+ if (!disableDefaultPropsExceptForClasses) {
+ if (__DEV__) {
+ // In production, this would just set it on the object.
+ let defaultProps;
+ // $FlowFixMe[prop-missing]
+ Object.defineProperties(lazyType, {
+ defaultProps: {
+ configurable: true,
+ get() {
+ return defaultProps;
+ },
+ // $FlowFixMe[missing-local-annot]
+ set(newDefaultProps) {
+ console.error(
+ 'It is not supported to assign `defaultProps` to ' +
+ 'a lazy component import. Either specify them where the component ' +
+ 'is defined, or create a wrapping component around it.',
+ );
+ defaultProps = newDefaultProps;
+ // Match production behavior more closely:
+ // $FlowFixMe[prop-missing]
+ Object.defineProperty(lazyType, 'defaultProps', {
+ enumerable: true,
+ });
+ },
},
- },
- });
+ });
+ }
}
return lazyType;
diff --git a/packages/react/src/__tests__/ReactElementClone-test.js b/packages/react/src/__tests__/ReactElementClone-test.js
index f16423ee60256..8dd9fceebc9c5 100644
--- a/packages/react/src/__tests__/ReactElementClone-test.js
+++ b/packages/react/src/__tests__/ReactElementClone-test.js
@@ -294,6 +294,7 @@ describe('ReactElementClone', () => {
);
});
+ // @gate !disableDefaultPropsExceptForClasses
it('should normalize props with default values', () => {
class Component extends React.Component {
render() {
diff --git a/packages/react/src/__tests__/forwardRef-test.js b/packages/react/src/__tests__/forwardRef-test.js
index 667da7bb48236..15726519eeb98 100644
--- a/packages/react/src/__tests__/forwardRef-test.js
+++ b/packages/react/src/__tests__/forwardRef-test.js
@@ -74,6 +74,7 @@ describe('forwardRef', () => {
expect(ref.current).toBe(null);
});
+ // @gate !disableDefaultPropsExceptForClasses
it('should support defaultProps', async () => {
function FunctionComponent({forwardedRef, optional, required}) {
return (
diff --git a/packages/react/src/jsx/ReactJSXElement.js b/packages/react/src/jsx/ReactJSXElement.js
index 3a0027c2b6a32..ffee6e1379706 100644
--- a/packages/react/src/jsx/ReactJSXElement.js
+++ b/packages/react/src/jsx/ReactJSXElement.js
@@ -18,7 +18,11 @@ import {checkKeyStringCoercion} from 'shared/CheckStringCoercion';
import isValidElementType from 'shared/isValidElementType';
import isArray from 'shared/isArray';
import {describeUnknownElementTypeFrameInDEV} from 'shared/ReactComponentStackFrame';
-import {enableRefAsProp, disableStringRefs} from 'shared/ReactFeatureFlags';
+import {
+ enableRefAsProp,
+ disableStringRefs,
+ disableDefaultPropsExceptForClasses,
+} from 'shared/ReactFeatureFlags';
const ReactCurrentOwner = ReactSharedInternals.ReactCurrentOwner;
const ReactDebugCurrentFrame = ReactSharedInternals.ReactDebugCurrentFrame;
@@ -340,12 +344,14 @@ export function jsxProd(type, config, maybeKey) {
}
}
- // Resolve default props
- if (type && type.defaultProps) {
- const defaultProps = type.defaultProps;
- for (propName in defaultProps) {
- if (props[propName] === undefined) {
- props[propName] = defaultProps[propName];
+ if (!disableDefaultPropsExceptForClasses) {
+ // Resolve default props
+ if (type && type.defaultProps) {
+ const defaultProps = type.defaultProps;
+ for (propName in defaultProps) {
+ if (props[propName] === undefined) {
+ props[propName] = defaultProps[propName];
+ }
}
}
}
@@ -554,12 +560,14 @@ export function jsxDEV(type, config, maybeKey, isStaticChildren, source, self) {
}
}
- // Resolve default props
- if (type && type.defaultProps) {
- const defaultProps = type.defaultProps;
- for (propName in defaultProps) {
- if (props[propName] === undefined) {
- props[propName] = defaultProps[propName];
+ if (!disableDefaultPropsExceptForClasses) {
+ // Resolve default props
+ if (type && type.defaultProps) {
+ const defaultProps = type.defaultProps;
+ for (propName in defaultProps) {
+ if (props[propName] === undefined) {
+ props[propName] = defaultProps[propName];
+ }
}
}
}
@@ -811,7 +819,11 @@ export function cloneElement(element, config, children) {
// Remaining properties override existing props
let defaultProps;
- if (element.type && element.type.defaultProps) {
+ if (
+ !disableDefaultPropsExceptForClasses &&
+ element.type &&
+ element.type.defaultProps
+ ) {
defaultProps = element.type.defaultProps;
}
for (propName in config) {
@@ -833,7 +845,11 @@ export function cloneElement(element, config, children) {
// backwards compatibility.
!(enableRefAsProp && propName === 'ref' && config.ref === undefined)
) {
- if (config[propName] === undefined && defaultProps !== undefined) {
+ if (
+ !disableDefaultPropsExceptForClasses &&
+ config[propName] === undefined &&
+ defaultProps !== undefined
+ ) {
// Resolve default props
props[propName] = defaultProps[propName];
} else {
diff --git a/packages/shared/ReactFeatureFlags.js b/packages/shared/ReactFeatureFlags.js
index c527d069dcaa5..43cbe66ea47a1 100644
--- a/packages/shared/ReactFeatureFlags.js
+++ b/packages/shared/ReactFeatureFlags.js
@@ -40,6 +40,9 @@ export const disableSchedulerTimeoutInWorkLoop = false;
// those can be fixed.
export const enableDeferRootSchedulingToMicrotask = true;
+// TODO: Land at Meta before removing.
+export const disableDefaultPropsExceptForClasses = true;
+
// -----------------------------------------------------------------------------
// Slated for removal in the future (significant effort)
//
diff --git a/packages/shared/forks/ReactFeatureFlags.native-fb-dynamic.js b/packages/shared/forks/ReactFeatureFlags.native-fb-dynamic.js
index 50c65b0038e16..f024eb4b1cd87 100644
--- a/packages/shared/forks/ReactFeatureFlags.native-fb-dynamic.js
+++ b/packages/shared/forks/ReactFeatureFlags.native-fb-dynamic.js
@@ -27,3 +27,4 @@ export const enableRenderableContext = __VARIANT__;
export const enableUnifiedSyncLane = __VARIANT__;
export const passChildrenWhenCloningPersistedNodes = __VARIANT__;
export const useModernStrictMode = __VARIANT__;
+export const disableDefaultPropsExceptForClasses = __VARIANT__;
diff --git a/packages/shared/forks/ReactFeatureFlags.native-fb.js b/packages/shared/forks/ReactFeatureFlags.native-fb.js
index 004c76d747569..c3aee2ab41bee 100644
--- a/packages/shared/forks/ReactFeatureFlags.native-fb.js
+++ b/packages/shared/forks/ReactFeatureFlags.native-fb.js
@@ -29,6 +29,7 @@ export const {
enableUnifiedSyncLane,
passChildrenWhenCloningPersistedNodes,
useModernStrictMode,
+ disableDefaultPropsExceptForClasses,
} = dynamicFlags;
// The rest of the flags are static for better dead code elimination.
diff --git a/packages/shared/forks/ReactFeatureFlags.native-oss.js b/packages/shared/forks/ReactFeatureFlags.native-oss.js
index 79f77bdc052ca..6f5293398b8c8 100644
--- a/packages/shared/forks/ReactFeatureFlags.native-oss.js
+++ b/packages/shared/forks/ReactFeatureFlags.native-oss.js
@@ -37,6 +37,7 @@ export const enableComponentStackLocations = __TODO_NEXT_RN_MAJOR__;
// -----------------------------------------------------------------------------
export const enableCache = __TODO_NEXT_RN_MAJOR__;
export const enableRenderableContext = __TODO_NEXT_RN_MAJOR__;
+export const disableDefaultPropsExceptForClasses = __TODO_NEXT_RN_MAJOR__;
// -----------------------------------------------------------------------------
// Already enabled for next React Native major.
diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.js
index cef2b788b73ce..1fe4a6e2d86d9 100644
--- a/packages/shared/forks/ReactFeatureFlags.test-renderer.js
+++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.js
@@ -90,6 +90,7 @@ export const disableLegacyContext = true;
export const disableDOMTestUtils = true;
export const enableRenderableContext = true;
export const enableReactTestRendererWarning = true;
+export const disableDefaultPropsExceptForClasses = true;
// Flow magic to verify the exports of this file match the original version.
((((null: any): ExportsType): FeatureFlagsType): ExportsType);
diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.native-fb.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.native-fb.js
index d12550dabbc1d..e78740263ff90 100644
--- a/packages/shared/forks/ReactFeatureFlags.test-renderer.native-fb.js
+++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.native-fb.js
@@ -85,5 +85,7 @@ export const enableReactTestRendererWarning = false;
export const disableLegacyMode = false;
export const disableDOMTestUtils = false;
+export const disableDefaultPropsExceptForClasses = false;
+
// Flow magic to verify the exports of this file match the original version.
((((null: any): ExportsType): FeatureFlagsType): ExportsType);
diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js
index 3bfc66a0069ba..a222f31812407 100644
--- a/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js
+++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js
@@ -85,5 +85,7 @@ export const enableReactTestRendererWarning = false;
export const disableLegacyMode = false;
export const disableDOMTestUtils = false;
+export const disableDefaultPropsExceptForClasses = false;
+
// Flow magic to verify the exports of this file match the original version.
((((null: any): ExportsType): FeatureFlagsType): ExportsType);
diff --git a/packages/shared/forks/ReactFeatureFlags.www-dynamic.js b/packages/shared/forks/ReactFeatureFlags.www-dynamic.js
index b7e953047e77d..63f2d57ea4eb6 100644
--- a/packages/shared/forks/ReactFeatureFlags.www-dynamic.js
+++ b/packages/shared/forks/ReactFeatureFlags.www-dynamic.js
@@ -27,6 +27,7 @@ export const enableRenderableContext = __VARIANT__;
export const enableRefAsProp = __VARIANT__;
export const enableRetryLaneExpiration = __VARIANT__;
export const favorSafetyOverHydrationPerf = __VARIANT__;
+export const disableDefaultPropsExceptForClasses = __VARIANT__;
export const retryLaneExpirationMs = 5000;
export const syncLaneExpirationMs = 250;
export const transitionLaneExpirationMs = 5000;
diff --git a/packages/shared/forks/ReactFeatureFlags.www.js b/packages/shared/forks/ReactFeatureFlags.www.js
index 3057542f39740..9d539dd8ecb78 100644
--- a/packages/shared/forks/ReactFeatureFlags.www.js
+++ b/packages/shared/forks/ReactFeatureFlags.www.js
@@ -34,6 +34,7 @@ export const {
enableRenderableContext,
enableRefAsProp,
favorSafetyOverHydrationPerf,
+ disableDefaultPropsExceptForClasses,
} = dynamicFeatureFlags;
// On WWW, __EXPERIMENTAL__ is used for a new modern build.