Cleanup disableDefaultPropsExceptForClasses flag (#33648)

This commit is contained in:
Jan Kassens
2025-07-01 15:52:56 -04:00
committed by GitHub
parent 91d097b2c5
commit 602917c8cb
21 changed files with 18 additions and 1023 deletions
@@ -14,7 +14,6 @@ let React;
let ReactTestRenderer;
let ReactDebugTools;
let act;
let assertConsoleErrorDev;
let useMemoCache;
function normalizeSourceLoc(tree) {
@@ -34,7 +33,7 @@ describe('ReactHooksInspectionIntegration', () => {
jest.resetModules();
React = require('react');
ReactTestRenderer = require('react-test-renderer');
({act, assertConsoleErrorDev} = require('internal-test-utils'));
({act} = require('internal-test-utils'));
ReactDebugTools = require('react-debug-tools');
useMemoCache = require('react/compiler-runtime').c;
});
@@ -2321,57 +2320,6 @@ describe('ReactHooksInspectionIntegration', () => {
});
});
// @gate !disableDefaultPropsExceptForClasses
it('should support defaultProps and lazy', async () => {
const Suspense = React.Suspense;
function Foo(props) {
const [value] = React.useState(props.defaultValue.slice(0, 3));
return <div>{value}</div>;
}
Foo.defaultProps = {
defaultValue: 'default',
};
async function fakeImport(result) {
return {default: result};
}
const LazyFoo = React.lazy(() => fakeImport(Foo));
const renderer = ReactTestRenderer.create(
<Suspense fallback="Loading...">
<LazyFoo />
</Suspense>,
);
await act(async () => await LazyFoo);
assertConsoleErrorDev([
'Foo: Support for defaultProps will be removed from function components in a future major release. Use JavaScript default parameters instead.',
]);
const childFiber = renderer.root._currentFiber();
const tree = ReactDebugTools.inspectHooksOfFiber(childFiber);
expect(normalizeSourceLoc(tree)).toMatchInlineSnapshot(`
[
{
"debugInfo": null,
"hookSource": {
"columnNumber": 0,
"fileName": "**",
"functionName": "Foo",
"lineNumber": 0,
},
"id": 0,
"isStateEditable": true,
"name": "State",
"subHooks": [],
"value": "def",
},
]
`);
});
// This test case is based on an open source bug report:
// https://github.com/facebookincubator/redux-react-hook/issues/34#issuecomment-466693787
it('should properly advance the current hook for useContext', async () => {
@@ -1,72 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @emails react-core
*/
'use strict';
let React;
let ReactNoop;
let waitForAll;
let assertConsoleErrorDev;
describe('ReactDeprecationWarnings', () => {
beforeEach(() => {
jest.resetModules();
React = require('react');
ReactNoop = require('react-noop-renderer');
const InternalTestUtils = require('internal-test-utils');
waitForAll = InternalTestUtils.waitForAll;
assertConsoleErrorDev = InternalTestUtils.assertConsoleErrorDev;
});
// @gate !disableDefaultPropsExceptForClasses || !__DEV__
it('should warn when given defaultProps', async () => {
function FunctionalComponent(props) {
return null;
}
FunctionalComponent.defaultProps = {
testProp: true,
};
ReactNoop.render(<FunctionalComponent />);
await waitForAll([]);
assertConsoleErrorDev([
'FunctionalComponent: Support for defaultProps ' +
'will be removed from function components in a future major ' +
'release. Use JavaScript default parameters instead.\n' +
' in FunctionalComponent (at **)',
]);
});
// @gate !disableDefaultPropsExceptForClasses || !__DEV__
it('should warn when given defaultProps on a memoized function', async () => {
const MemoComponent = React.memo(function FunctionalComponent(props) {
return null;
});
MemoComponent.defaultProps = {
testProp: true,
};
ReactNoop.render(
<div>
<MemoComponent />
</div>,
);
await waitForAll([]);
assertConsoleErrorDev(
[
'FunctionalComponent: Support for defaultProps ' +
'will be removed from memo components in a future major ' +
'release. Use JavaScript default parameters instead.',
],
{withoutStack: true},
);
});
});
@@ -205,27 +205,6 @@ describe('ReactFunctionComponent', () => {
]);
});
// @gate !disableDefaultPropsExceptForClasses
it('should support default props', async () => {
function Child(props) {
return <div>{props.test}</div>;
}
Child.defaultProps = {test: 2};
const container = document.createElement('div');
const root = ReactDOMClient.createRoot(container);
await act(() => {
root.render(<Child />);
});
expect(container.textContent).toBe('2');
assertConsoleErrorDev([
'Child: Support for defaultProps will be removed from function components in a future major release. ' +
'Use JavaScript default parameters instead.\n' +
' in Child (at **)',
]);
});
// @gate !disableLegacyContext && !disableLegacyContextForFunctionComponents
it('should receive context', async () => {
class Parent extends React.Component {
+9 -86
View File
@@ -117,7 +117,6 @@ import {
enableCPUSuspense,
enablePostpone,
disableLegacyMode,
disableDefaultPropsExceptForClasses,
enableHydrationLaneScheduling,
enableViewTransition,
enableFragmentRefs,
@@ -258,7 +257,6 @@ import {
updateClassInstance,
resolveClassComponentProps,
} from './ReactFiberClassComponent';
import {resolveDefaultPropsOnNonClassComponent} from './ReactFiberLazyComponent';
import {
createFiberFromTypeAndProps,
createFiberFromFragment,
@@ -327,7 +325,6 @@ let didWarnAboutGetDerivedStateOnFunctionComponent;
export let didWarnAboutReassigningProps: boolean;
let didWarnAboutRevealOrder;
let didWarnAboutTailOptions;
let didWarnAboutDefaultPropsOnFunctionComponent;
let didWarnAboutClassNameOnViewTransition;
if (__DEV__) {
@@ -338,7 +335,6 @@ if (__DEV__) {
didWarnAboutReassigningProps = false;
didWarnAboutRevealOrder = ({}: {[string]: boolean});
didWarnAboutTailOptions = ({}: {[string]: boolean});
didWarnAboutDefaultPropsOnFunctionComponent = ({}: {[string]: boolean});
didWarnAboutClassNameOnViewTransition = ({}: {[string]: boolean});
}
@@ -482,13 +478,7 @@ function updateMemoComponent(
): null | Fiber {
if (current === null) {
const type = Component.type;
if (
isSimpleFunctionComponent(type) &&
Component.compare === null &&
// SimpleMemoComponent codepath doesn't resolve outer props either.
(disableDefaultPropsExceptForClasses ||
Component.defaultProps === undefined)
) {
if (isSimpleFunctionComponent(type) && Component.compare === null) {
let resolvedType = type;
if (__DEV__) {
resolvedType = resolveFunctionForHotReloading(type);
@@ -509,21 +499,6 @@ function updateMemoComponent(
renderLanes,
);
}
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;
}
}
}
}
const child = createFiberFromTypeAndProps(
Component.type,
null,
@@ -2072,9 +2047,6 @@ function mountLazyComponent(
renderLanes,
);
} else {
const resolvedProps = disableDefaultPropsExceptForClasses
? props
: resolveDefaultPropsOnNonClassComponent(Component, props);
workInProgress.tag = FunctionComponent;
if (__DEV__) {
validateFunctionComponentInDev(workInProgress, Component);
@@ -2085,16 +2057,13 @@ function mountLazyComponent(
null,
workInProgress,
Component,
resolvedProps,
props,
renderLanes,
);
}
} else if (Component !== undefined && Component !== null) {
const $$typeof = Component.$$typeof;
if ($$typeof === REACT_FORWARD_REF_TYPE) {
const resolvedProps = disableDefaultPropsExceptForClasses
? props
: resolveDefaultPropsOnNonClassComponent(Component, props);
workInProgress.tag = ForwardRef;
if (__DEV__) {
workInProgress.type = Component =
@@ -2104,24 +2073,16 @@ function mountLazyComponent(
null,
workInProgress,
Component,
resolvedProps,
props,
renderLanes,
);
} else if ($$typeof === REACT_MEMO_TYPE) {
const resolvedProps = disableDefaultPropsExceptForClasses
? props
: resolveDefaultPropsOnNonClassComponent(Component, props);
workInProgress.tag = MemoComponent;
return updateMemoComponent(
null,
workInProgress,
Component,
disableDefaultPropsExceptForClasses
? resolvedProps
: resolveDefaultPropsOnNonClassComponent(
Component.type,
resolvedProps,
), // The inner type can have defaults too
props,
renderLanes,
);
}
@@ -2198,22 +2159,6 @@ function validateFunctionComponentInDev(workInProgress: Fiber, Component: any) {
);
}
if (
!disableDefaultPropsExceptForClasses &&
Component.defaultProps !== undefined
) {
const componentName = getComponentNameFromType(Component) || 'Unknown';
if (!didWarnAboutDefaultPropsOnFunctionComponent[componentName]) {
console.error(
'%s: Support for defaultProps will be removed from function components ' +
'in a future major release. Use JavaScript default parameters instead.',
componentName,
);
didWarnAboutDefaultPropsOnFunctionComponent[componentName] = true;
}
}
if (typeof Component.getDerivedStateFromProps === 'function') {
const componentName = getComponentNameFromType(Component) || 'Unknown';
@@ -4175,17 +4120,11 @@ function beginWork(
}
case FunctionComponent: {
const Component = workInProgress.type;
const unresolvedProps = workInProgress.pendingProps;
const resolvedProps =
disableDefaultPropsExceptForClasses ||
workInProgress.elementType === Component
? unresolvedProps
: resolveDefaultPropsOnNonClassComponent(Component, unresolvedProps);
return updateFunctionComponent(
current,
workInProgress,
Component,
resolvedProps,
workInProgress.pendingProps,
renderLanes,
);
}
@@ -4226,18 +4165,11 @@ function beginWork(
case HostPortal:
return updatePortalComponent(current, workInProgress, renderLanes);
case ForwardRef: {
const type = workInProgress.type;
const unresolvedProps = workInProgress.pendingProps;
const resolvedProps =
disableDefaultPropsExceptForClasses ||
workInProgress.elementType === type
? unresolvedProps
: resolveDefaultPropsOnNonClassComponent(type, unresolvedProps);
return updateForwardRef(
current,
workInProgress,
type,
resolvedProps,
workInProgress.type,
workInProgress.pendingProps,
renderLanes,
);
}
@@ -4252,20 +4184,11 @@ function beginWork(
case ContextConsumer:
return updateContextConsumer(current, workInProgress, renderLanes);
case MemoComponent: {
const type = workInProgress.type;
const unresolvedProps = workInProgress.pendingProps;
// Resolve outer props first, then resolve inner props.
let resolvedProps = disableDefaultPropsExceptForClasses
? unresolvedProps
: resolveDefaultPropsOnNonClassComponent(type, unresolvedProps);
resolvedProps = disableDefaultPropsExceptForClasses
? resolvedProps
: resolveDefaultPropsOnNonClassComponent(type.type, resolvedProps);
return updateMemoComponent(
current,
workInProgress,
type,
resolvedProps,
workInProgress.type,
workInProgress.pendingProps,
renderLanes,
);
}
+1 -7
View File
@@ -20,7 +20,6 @@ import {
import {
disableLegacyContext,
enableSchedulingProfiler,
disableDefaultPropsExceptForClasses,
} from 'shared/ReactFeatureFlags';
import ReactStrictModeWarnings from './ReactStrictModeWarnings';
import {get as getInstance, set as setInstance} from 'shared/ReactInstanceMap';
@@ -1214,12 +1213,7 @@ export function resolveClassComponentProps(
// Resolve default props.
const defaultProps = Component.defaultProps;
if (
defaultProps &&
// If disableDefaultPropsExceptForClasses is true, we always resolve
// default props here in the reconciler, rather than in the JSX runtime.
(disableDefaultPropsExceptForClasses || !alreadyResolvedDefaultProps)
) {
if (defaultProps) {
// We may have already copied the props object above to remove ref. If so,
// we can modify that. Otherwise, copy the props object with Object.assign.
if (newProps === baseProps) {
@@ -1,34 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
import assign from 'shared/assign';
import {disableDefaultPropsExceptForClasses} from 'shared/ReactFeatureFlags';
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);
const defaultProps = Component.defaultProps;
for (const propName in defaultProps) {
if (props[propName] === undefined) {
props[propName] = defaultProps[propName];
}
}
return props;
}
return baseProps;
}
+2 -17
View File
@@ -46,7 +46,6 @@ import {
alwaysThrottleRetries,
enableInfiniteRenderLoopDetection,
disableLegacyMode,
disableDefaultPropsExceptForClasses,
enableComponentPerformanceTrack,
enableYieldingBeforePassive,
enableThrottledScheduling,
@@ -349,7 +348,6 @@ import {
getSuspenseHandler,
getShellBoundary,
} from './ReactFiberSuspenseContext';
import {resolveDefaultPropsOnNonClassComponent} from './ReactFiberLazyComponent';
import {resetChildReconcilerOnUnwind} from './ReactChildFiber';
import {
ensureRootIsScheduled,
@@ -2858,12 +2856,6 @@ function replayBeginWork(unitOfWork: Fiber): null | Fiber {
// could maybe use this as an opportunity to say `use` doesn't work with
// `defaultProps` :)
const Component = unitOfWork.type;
const unresolvedProps = unitOfWork.pendingProps;
const resolvedProps =
disableDefaultPropsExceptForClasses ||
unitOfWork.elementType === Component
? unresolvedProps
: resolveDefaultPropsOnNonClassComponent(Component, unresolvedProps);
let context: any;
if (!disableLegacyContext) {
const unmaskedContext = getUnmaskedContext(unitOfWork, Component, true);
@@ -2872,7 +2864,7 @@ function replayBeginWork(unitOfWork: Fiber): null | Fiber {
next = replayFunctionComponent(
current,
unitOfWork,
resolvedProps,
unitOfWork.pendingProps,
Component,
context,
workInProgressRootRenderLanes,
@@ -2885,17 +2877,10 @@ function replayBeginWork(unitOfWork: Fiber): null | Fiber {
// could maybe use this as an opportunity to say `use` doesn't work with
// `defaultProps` :)
const Component = unitOfWork.type.render;
const unresolvedProps = unitOfWork.pendingProps;
const resolvedProps =
disableDefaultPropsExceptForClasses ||
unitOfWork.elementType === Component
? unresolvedProps
: resolveDefaultPropsOnNonClassComponent(Component, unresolvedProps);
next = replayFunctionComponent(
current,
unitOfWork,
resolvedProps,
unitOfWork.pendingProps,
Component,
unitOfWork.ref,
workInProgressRootRenderLanes,
@@ -701,64 +701,6 @@ 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);
return props.inner + ' ' + props.outer;
}
T.defaultProps = {inner: 'Hi'};
const LazyText = lazy(() => fakeImport(T));
LazyText.defaultProps = {outer: 'Bye'};
assertConsoleErrorDev(
[
'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.',
],
{withoutStack: true},
);
const root = ReactTestRenderer.create(
<Suspense fallback={<Text text="Loading..." />}>
<LazyText />
</Suspense>,
{
unstable_isConcurrent: true,
},
);
await waitForAll(['Loading...']);
expect(root).not.toMatchRenderedOutput('Hi Bye');
await act(() => resolveFakeImport(T));
assertLog(['Hi Bye']);
assertConsoleErrorDev([
'T: Support for defaultProps ' +
'will be removed from function components in a future major ' +
'release. Use JavaScript default parameters instead.\n' +
' in T (at **)',
]);
expect(root).toMatchRenderedOutput('Hi Bye');
root.update(
<Suspense fallback={<Text text="Loading..." />}>
<LazyText outer="World" />
</Suspense>,
);
await waitForAll(['Hi World']);
expect(root).toMatchRenderedOutput('Hi World');
root.update(
<Suspense fallback={<Text text="Loading..." />}>
<LazyText inner="Friends" />
</Suspense>,
);
await waitForAll(['Friends Bye']);
expect(root).toMatchRenderedOutput('Friends Bye');
});
it('throws with a useful error when wrapping invalid type with lazy()', async () => {
const BadLazy = lazy(() => fakeImport(42));
@@ -1112,48 +1054,6 @@ describe('ReactLazy', () => {
);
});
// @gate !disableDefaultPropsExceptForClasses
it('resolves props for function component with defaultProps', async () => {
function Add(props) {
expect(props.innerWithDefault).toBe(42);
return props.inner + props.outer;
}
Add.defaultProps = {
innerWithDefault: 42,
};
const LazyAdd = lazy(() => fakeImport(Add));
const root = ReactTestRenderer.create(
<Suspense fallback={<Text text="Loading..." />}>
<LazyAdd inner="2" outer="2" />
</Suspense>,
{
unstable_isConcurrent: true,
},
);
await waitForAll(['Loading...']);
expect(root).not.toMatchRenderedOutput('22');
// Mount
await act(() => resolveFakeImport(Add));
assertConsoleErrorDev([
'Add: Support for defaultProps will be removed from function components in a future major release. Use JavaScript default parameters instead.\n' +
' in Add (at **)',
]);
expect(root).toMatchRenderedOutput('22');
// Update
root.update(
<Suspense fallback={<Text text="Loading..." />}>
<LazyAdd inner={false} outer={false} />
</Suspense>,
);
await waitForAll([]);
expect(root).toMatchRenderedOutput('0');
});
it('resolves props for function component without defaultProps', async () => {
function Add(props) {
return props.inner + props.outer;
@@ -1258,44 +1158,6 @@ describe('ReactLazy', () => {
expect(root).toMatchRenderedOutput('0');
});
// @gate !disableDefaultPropsExceptForClasses
it('resolves props for forwardRef component with defaultProps', async () => {
const Add = React.forwardRef((props, ref) => {
expect(props.innerWithDefault).toBe(42);
return props.inner + props.outer;
});
Add.displayName = 'Add';
Add.defaultProps = {
innerWithDefault: 42,
};
const LazyAdd = lazy(() => fakeImport(Add));
const root = ReactTestRenderer.create(
<Suspense fallback={<Text text="Loading..." />}>
<LazyAdd inner="2" outer="2" />
</Suspense>,
{
unstable_isConcurrent: true,
},
);
await waitForAll(['Loading...']);
expect(root).not.toMatchRenderedOutput('22');
// Mount
await act(() => resolveFakeImport(Add));
expect(root).toMatchRenderedOutput('22');
// Update
root.update(
<Suspense fallback={<Text text="Loading..." />}>
<LazyAdd inner={false} outer={false} />
</Suspense>,
);
await waitForAll([]);
expect(root).toMatchRenderedOutput('0');
});
it('resolves props for forwardRef component without defaultProps', async () => {
const Add = React.forwardRef((props, ref) => {
return props.inner + props.outer;
@@ -1330,51 +1192,6 @@ describe('ReactLazy', () => {
expect(root).toMatchRenderedOutput('0');
});
// @gate !disableDefaultPropsExceptForClasses
it('resolves props for outer memo component with defaultProps', async () => {
let Add = props => {
expect(props.innerWithDefault).toBe(42);
return props.inner + props.outer;
};
Add = React.memo(Add);
Add.defaultProps = {
innerWithDefault: 42,
};
const LazyAdd = lazy(() => fakeImport(Add));
const root = ReactTestRenderer.create(
<Suspense fallback={<Text text="Loading..." />}>
<LazyAdd inner="2" outer="2" />
</Suspense>,
{
unstable_isConcurrent: true,
},
);
await waitForAll(['Loading...']);
expect(root).not.toMatchRenderedOutput('22');
// Mount
await act(() => resolveFakeImport(Add));
assertConsoleErrorDev(
[
'Add: Support for defaultProps will be removed from memo components in a future major release. Use JavaScript default parameters instead.',
],
{withoutStack: true},
);
expect(root).toMatchRenderedOutput('22');
// Update
root.update(
<Suspense fallback={<Text text="Loading..." />}>
<LazyAdd inner={false} outer={false} />
</Suspense>,
);
await waitForAll([]);
expect(root).toMatchRenderedOutput('0');
});
it('resolves props for outer memo component without defaultProps', async () => {
let Add = props => {
return props.inner + props.outer;
@@ -1408,52 +1225,6 @@ describe('ReactLazy', () => {
expect(root).toMatchRenderedOutput('0');
});
// @gate !disableDefaultPropsExceptForClasses
it('resolves props for inner memo component with defaultProps', async () => {
const Add = props => {
expect(props.innerWithDefault).toBe(42);
return props.inner + props.outer;
};
Add.displayName = 'Add';
Add.defaultProps = {
innerWithDefault: 42,
};
const MemoAdd = React.memo(Add);
const LazyAdd = lazy(() => fakeImport(MemoAdd));
const root = ReactTestRenderer.create(
<Suspense fallback={<Text text="Loading..." />}>
<LazyAdd inner="2" outer="2" />
</Suspense>,
{
unstable_isConcurrent: true,
},
);
await waitForAll(['Loading...']);
expect(root).not.toMatchRenderedOutput('22');
// Mount
await act(() => resolveFakeImport(MemoAdd));
assertConsoleErrorDev(
[
'Add: Support for defaultProps will be removed from function components in a future major release. Use JavaScript default parameters instead.',
],
{withoutStack: true},
);
expect(root).toMatchRenderedOutput('22');
// Update
root.update(
<Suspense fallback={<Text text="Loading..." />}>
<LazyAdd inner={false} outer={false} />
</Suspense>,
);
await waitForAll([]);
expect(root).toMatchRenderedOutput('0');
});
it('resolves props for inner memo component without defaultProps', async () => {
const Add = props => {
return props.inner + props.outer;
@@ -1487,50 +1258,6 @@ describe('ReactLazy', () => {
expect(root).toMatchRenderedOutput('0');
});
// @gate !disableDefaultPropsExceptForClasses
it('uses outer resolved props on memo', async () => {
let T = props => {
return <Text text={props.text} />;
};
T.defaultProps = {
text: 'Inner default text',
};
T = React.memo(T);
const LazyText = lazy(() => fakeImport(T));
const root = ReactTestRenderer.create(
<Suspense fallback={<Text text="Loading..." />}>
<LazyText />
</Suspense>,
{
unstable_isConcurrent: true,
},
);
await waitForAll(['Loading...']);
expect(root).not.toMatchRenderedOutput('Inner default text');
// Mount
await act(() => resolveFakeImport(T));
assertLog(['Inner default text']);
assertConsoleErrorDev(
[
'T: Support for defaultProps will be removed from function components in a future major release. ' +
'Use JavaScript default parameters instead.',
],
{withoutStack: true},
);
expect(root).toMatchRenderedOutput('Inner default text');
// Update
root.update(
<Suspense fallback={<Text text="Loading..." />}>
<LazyText text={null} />
</Suspense>,
);
await waitForAll([null]);
expect(root).toMatchRenderedOutput(null);
});
it('includes lazy-loaded component in warning stack', async () => {
const Foo = props => <div>{[<Text text="A" />, <Text text="B" />]}</div>;
const LazyFoo = lazy(() => {
@@ -1614,150 +1341,6 @@ describe('ReactLazy', () => {
expect(ref.current).not.toBe(null);
});
// 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;
});
Add.defaultProps = {
inner: 2,
};
const LazyAdd = lazy(() => fakeImport(Add));
const root = ReactTestRenderer.create(
<Suspense fallback={<Text text="Loading..." />}>
<LazyAdd outer={2} />
</Suspense>,
{
unstable_isConcurrent: true,
},
);
await waitForAll(['Loading...']);
expect(root).not.toMatchRenderedOutput('4');
// Mount
await act(() => resolveFakeImport(Add));
assertConsoleErrorDev(
[
'Unknown: Support for defaultProps will be removed from memo components in a future major release. ' +
'Use JavaScript default parameters instead.',
],
{withoutStack: true},
);
expect(root).toMatchRenderedOutput('4');
// Update (shallowly equal)
root.update(
<Suspense fallback={<Text text="Loading..." />}>
<LazyAdd outer={2} />
</Suspense>,
);
await waitForAll([]);
expect(root).toMatchRenderedOutput('4');
// Update
root.update(
<Suspense fallback={<Text text="Loading..." />}>
<LazyAdd outer={3} />
</Suspense>,
);
await waitForAll([]);
expect(root).toMatchRenderedOutput('5');
// Update (shallowly equal)
root.update(
<Suspense fallback={<Text text="Loading..." />}>
<LazyAdd outer={3} />
</Suspense>,
);
await waitForAll([]);
expect(root).toMatchRenderedOutput('5');
// Update (explicit props)
root.update(
<Suspense fallback={<Text text="Loading..." />}>
<LazyAdd outer={1} inner={1} />
</Suspense>,
);
await waitForAll([]);
expect(root).toMatchRenderedOutput('2');
// Update (explicit props, shallowly equal)
root.update(
<Suspense fallback={<Text text="Loading..." />}>
<LazyAdd outer={1} inner={1} />
</Suspense>,
);
await waitForAll([]);
expect(root).toMatchRenderedOutput('2');
// Update
root.update(
<Suspense fallback={<Text text="Loading..." />}>
<LazyAdd outer={1} />
</Suspense>,
);
await waitForAll([]);
expect(root).toMatchRenderedOutput('3');
});
// @gate !disableDefaultPropsExceptForClasses
it('merges defaultProps in the correct order', async () => {
let Add = React.memo(props => {
return props.inner + props.outer;
});
Add.defaultProps = {
inner: 100,
};
Add = React.memo(Add);
Add.defaultProps = {
inner: 2,
outer: 0,
};
const LazyAdd = lazy(() => fakeImport(Add));
const root = ReactTestRenderer.create(
<Suspense fallback={<Text text="Loading..." />}>
<LazyAdd outer={2} />
</Suspense>,
{
unstable_isConcurrent: true,
},
);
await waitForAll(['Loading...']);
expect(root).not.toMatchRenderedOutput('4');
// Mount
await act(() => resolveFakeImport(Add));
assertConsoleErrorDev(
[
'Memo: Support for defaultProps will be removed from memo components in a future major release. ' +
'Use JavaScript default parameters instead.',
'Unknown: Support for defaultProps will be removed from memo components in a future major release. ' +
'Use JavaScript default parameters instead.',
],
{withoutStack: true},
);
expect(root).toMatchRenderedOutput('4');
// Update
root.update(
<Suspense fallback={<Text text="Loading..." />}>
<LazyAdd outer={3} />
</Suspense>,
);
await waitForAll([]);
expect(root).toMatchRenderedOutput('5');
// Update
root.update(
<Suspense fallback={<Text text="Loading..." />}>
<LazyAdd />
</Suspense>,
);
await waitForAll([]);
expect(root).toMatchRenderedOutput('2');
});
it('should error with a component stack naming the resolved component', async () => {
let componentStackMessage;
@@ -373,77 +373,6 @@ describe('memo', () => {
expect(ReactNoop).toMatchRenderedOutput(<span prop="1!" />);
});
// @gate !disableDefaultPropsExceptForClasses
it('supports defaultProps defined on the memo() return value', async () => {
function Counter({a, b, c, d, e}) {
return <Text text={a + b + c + d + e} />;
}
Counter.defaultProps = {
a: 1,
};
// Note! We intentionally use React.memo() rather than the injected memo().
// This tests a synchronous chain of React.memo() without lazy() in the middle.
Counter = React.memo(Counter);
Counter.defaultProps = {
b: 2,
};
Counter = React.memo(Counter);
Counter = React.memo(Counter); // Layer without defaultProps
Counter.defaultProps = {
c: 3,
};
Counter = React.memo(Counter);
Counter.defaultProps = {
d: 4,
};
// The final layer uses memo() from test fixture (which might be lazy).
Counter = memo(Counter);
await act(() => {
ReactNoop.render(
<Suspense fallback={<Text text="Loading..." />}>
<Counter e={5} />
</Suspense>,
);
});
assertLog(['Loading...', 15]);
if (label === 'lazy') {
assertConsoleErrorDev(
[
'Counter: Support for defaultProps will be removed from memo components in a future major release. ' +
'Use JavaScript default parameters instead.',
],
{withoutStack: true},
);
} else {
assertConsoleErrorDev([
'Counter: Support for defaultProps will be removed from memo components in a future major release. ' +
'Use JavaScript default parameters instead.\n' +
' in Indirection (at **)',
]);
}
expect(ReactNoop).toMatchRenderedOutput(<span prop={15} />);
// Should bail out because props have not changed
ReactNoop.render(
<Suspense>
<Counter e={5} />
</Suspense>,
);
await waitForAll([]);
expect(ReactNoop).toMatchRenderedOutput(<span prop={15} />);
// Should update because count prop changed
ReactNoop.render(
<Suspense>
<Counter e={10} />
</Suspense>,
);
await waitForAll([20]);
expect(ReactNoop).toMatchRenderedOutput(<span prop={20} />);
});
it('warns if the first argument is undefined', () => {
memo();
assertConsoleErrorDev(
@@ -466,46 +395,6 @@ describe('memo', () => {
);
});
// @gate !disableDefaultPropsExceptForClasses
it('handles nested defaultProps declarations', async () => {
function Inner(props) {
return props.inner + props.middle + props.outer;
}
Inner.defaultProps = {inner: 1};
const Middle = React.memo(Inner);
Middle.defaultProps = {middle: 10};
const Outer = React.memo(Middle);
Outer.defaultProps = {outer: 100};
const root = ReactNoop.createRoot();
await act(() => {
root.render(
<div>
<Outer />
</div>,
);
});
assertConsoleErrorDev(
[
'Inner: ' +
'Support for defaultProps will be removed from memo components in a future major release. ' +
'Use JavaScript default parameters instead.',
],
{withoutStack: true},
);
expect(root).toMatchRenderedOutput(<div>111</div>);
await act(async () => {
root.render(
<div>
<Outer inner="2" middle="3" outer="4" />
</div>,
);
await waitForAll([]);
});
expect(root).toMatchRenderedOutput(<div>234</div>);
});
it('does not drop lower priority state updates when bailing out at higher pri (simple)', async () => {
const {useState} = React;
+3 -57
View File
@@ -178,7 +178,6 @@ import {
enableScopeAPI,
enablePostpone,
enableHalt,
disableDefaultPropsExceptForClasses,
enableAsyncIterableChildren,
enableViewTransition,
enableFizzBlockingRender,
@@ -2399,12 +2398,7 @@ export function resolveClassComponentProps(
// Resolve default props.
const defaultProps = Component.defaultProps;
if (
defaultProps &&
// If disableDefaultPropsExceptForClasses is true, we always resolve
// default props here, rather than in the JSX runtime.
disableDefaultPropsExceptForClasses
) {
if (defaultProps) {
// We may have already copied the props object above to remove ref. If so,
// we can modify that. Otherwise, copy the props object with Object.assign.
if (newProps === baseProps) {
@@ -2453,7 +2447,6 @@ const didWarnAboutContextTypes: {[string]: boolean} = {};
const didWarnAboutContextTypeOnFunctionComponent: {[string]: boolean} = {};
const didWarnAboutGetDerivedStateOnFunctionComponent: {[string]: boolean} = {};
let didWarnAboutReassigningProps = false;
const didWarnAboutDefaultPropsOnFunctionComponent: {[string]: boolean} = {};
let didWarnAboutGenerators = false;
let didWarnAboutMaps = false;
@@ -2610,22 +2603,6 @@ function validateFunctionComponentInDev(Component: any): void {
);
}
if (
!disableDefaultPropsExceptForClasses &&
Component.defaultProps !== undefined
) {
const componentName = getComponentNameFromType(Component) || 'Unknown';
if (!didWarnAboutDefaultPropsOnFunctionComponent[componentName]) {
console.error(
'%s: Support for defaultProps will be removed from function components ' +
'in a future major release. Use JavaScript default parameters instead.',
componentName,
);
didWarnAboutDefaultPropsOnFunctionComponent[componentName] = true;
}
}
if (typeof Component.getDerivedStateFromProps === 'function') {
const componentName = getComponentNameFromType(Component) || 'Unknown';
@@ -2655,29 +2632,6 @@ function validateFunctionComponentInDev(Component: any): void {
}
}
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);
const defaultProps = Component.defaultProps;
for (const propName in defaultProps) {
if (props[propName] === undefined) {
props[propName] = defaultProps[propName];
}
}
return props;
}
return baseProps;
}
function renderForwardRef(
request: Request,
task: Task,
@@ -2735,11 +2689,7 @@ function renderMemo(
ref: any,
): void {
const innerType = type.type;
const resolvedProps = resolveDefaultPropsOnNonClassComponent(
innerType,
props,
);
renderElement(request, task, keyPath, innerType, resolvedProps, ref);
renderElement(request, task, keyPath, innerType, props, ref);
}
function renderContextConsumer(
@@ -2819,11 +2769,7 @@ function renderLazyComponent(
// eslint-disable-next-line no-throw-literal
throw null;
}
const resolvedProps = resolveDefaultPropsOnNonClassComponent(
Component,
props,
);
renderElement(request, task, keyPath, Component, resolvedProps, ref);
renderElement(request, task, keyPath, Component, props, ref);
}
function renderActivity(
-31
View File
@@ -10,7 +10,6 @@
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;
@@ -141,35 +140,5 @@ export function lazy<T>(
_init: lazyInitializer,
};
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;
}
@@ -291,28 +291,6 @@ describe('ReactElementClone', () => {
);
});
// @gate !disableDefaultPropsExceptForClasses
it('should normalize props with default values', () => {
class Component extends React.Component {
render() {
return <span />;
}
}
Component.defaultProps = {prop: 'testKey'};
const instance = React.createElement(Component);
const clonedInstance = React.cloneElement(instance, {prop: undefined});
expect(clonedInstance.props.prop).toBe('testKey');
const clonedInstance2 = React.cloneElement(instance, {prop: null});
expect(clonedInstance2.props.prop).toBe(null);
const instance2 = React.createElement(Component, {prop: 'newTestKey'});
const cloneInstance3 = React.cloneElement(instance2, {prop: undefined});
expect(cloneInstance3.props.prop).toBe('testKey');
const cloneInstance4 = React.cloneElement(instance2, {});
expect(cloneInstance4.props.prop).toBe('newTestKey');
});
it('warns for keys for arrays of elements in rest args', async () => {
const root = ReactDOMClient.createRoot(document.createElement('div'));
await act(() => {
@@ -76,45 +76,6 @@ describe('forwardRef', () => {
expect(ref.current).toBe(null);
});
// @gate !disableDefaultPropsExceptForClasses
it('should support defaultProps', async () => {
function FunctionComponent({forwardedRef, optional, required}) {
return (
<div ref={forwardedRef}>
{optional}
{required}
</div>
);
}
const RefForwardingComponent = React.forwardRef(
function NamedFunction(props, ref) {
return <FunctionComponent {...props} forwardedRef={ref} />;
},
);
RefForwardingComponent.defaultProps = {
optional: 'default',
};
const ref = React.createRef();
ReactNoop.render(
<RefForwardingComponent ref={ref} optional="foo" required="bar" />,
);
await waitForAll([]);
expect(ref.current.children).toEqual([
{text: 'foo', hidden: false},
{text: 'bar', hidden: false},
]);
ReactNoop.render(<RefForwardingComponent ref={ref} required="foo" />);
await waitForAll([]);
expect(ref.current.children).toEqual([
{text: 'default', hidden: false},
{text: 'foo', hidden: false},
]);
});
it('should warn if not provided a callback during creation', () => {
React.forwardRef(undefined);
assertConsoleErrorDev(
+2 -46
View File
@@ -16,10 +16,7 @@ import {
} from 'shared/ReactSymbols';
import {checkKeyStringCoercion} from 'shared/CheckStringCoercion';
import isArray from 'shared/isArray';
import {
disableDefaultPropsExceptForClasses,
ownerStackLimit,
} from 'shared/ReactFeatureFlags';
import {ownerStackLimit} from 'shared/ReactFeatureFlags';
const createTask =
// eslint-disable-next-line react-internal/no-production-logging
@@ -351,18 +348,6 @@ export function jsxProd(type, config, maybeKey) {
}
}
if (!disableDefaultPropsExceptForClasses) {
// Resolve default props
if (type && type.defaultProps) {
const defaultProps = type.defaultProps;
for (const propName in defaultProps) {
if (props[propName] === undefined) {
props[propName] = defaultProps[propName];
}
}
}
}
return ReactElement(
type,
key,
@@ -598,18 +583,6 @@ function jsxDEVImpl(
}
}
if (!disableDefaultPropsExceptForClasses) {
// Resolve default props
if (type && type.defaultProps) {
const defaultProps = type.defaultProps;
for (const propName in defaultProps) {
if (props[propName] === undefined) {
props[propName] = defaultProps[propName];
}
}
}
}
if (key) {
const displayName =
typeof type === 'function'
@@ -817,14 +790,6 @@ export function cloneElement(element, config, children) {
}
// Remaining properties override existing props
let defaultProps;
if (
!disableDefaultPropsExceptForClasses &&
element.type &&
element.type.defaultProps
) {
defaultProps = element.type.defaultProps;
}
for (propName in config) {
if (
hasOwnProperty.call(config, propName) &&
@@ -843,16 +808,7 @@ export function cloneElement(element, config, children) {
// backwards compatibility.
!(propName === 'ref' && config.ref === undefined)
) {
if (
!disableDefaultPropsExceptForClasses &&
config[propName] === undefined &&
defaultProps !== undefined
) {
// Resolve default props
props[propName] = defaultProps[propName];
} else {
props[propName] = config[propName];
}
props[propName] = config[propName];
}
}
}
-3
View File
@@ -37,9 +37,6 @@ export const favorSafetyOverHydrationPerf = true;
// Need to remove didTimeout argument from Scheduler before landing
export const disableSchedulerTimeoutInWorkLoop = false;
// TODO: Land at Meta before removing.
export const disableDefaultPropsExceptForClasses = true;
// -----------------------------------------------------------------------------
// Slated for removal in the future (significant effort)
//
@@ -33,7 +33,6 @@ export const {
// The rest of the flags are static for better dead code elimination.
export const disableClientCache = true;
export const disableCommentsAsDOMContainers = true;
export const disableDefaultPropsExceptForClasses = true;
export const disableInputAttributeSyncing = false;
export const disableLegacyContext = false;
export const disableLegacyContextForFunctionComponents = false;
@@ -18,7 +18,6 @@ import typeof * as ExportsType from './ReactFeatureFlags.native-oss';
export const alwaysThrottleRetries = false;
export const disableClientCache = true;
export const disableCommentsAsDOMContainers = true;
export const disableDefaultPropsExceptForClasses = true;
export const disableInputAttributeSyncing = false;
export const disableLegacyContext = true;
export const disableLegacyContextForFunctionComponents = true;
@@ -89,7 +89,6 @@ export const disableLegacyMode = true;
export const disableLegacyContext = true;
export const disableLegacyContextForFunctionComponents = true;
export const enableReactTestRendererWarning = true;
export const disableDefaultPropsExceptForClasses = true;
export const enableObjectFiber = false;
@@ -63,8 +63,6 @@ export const enableInfiniteRenderLoopDetection = false;
export const enableReactTestRendererWarning = false;
export const disableLegacyMode = true;
export const disableDefaultPropsExceptForClasses = true;
export const renameElementSymbol = false;
export const enableObjectFiber = false;
@@ -14,7 +14,6 @@
// with the __VARIANT__ set to `true`, and once set to `false`.
export const alwaysThrottleRetries = __VARIANT__;
export const disableDefaultPropsExceptForClasses = __VARIANT__;
export const disableLegacyContextForFunctionComponents = __VARIANT__;
export const disableSchedulerTimeoutInWorkLoop = __VARIANT__;
export const enableHiddenSubtreeInsertionEffectCleanup = __VARIANT__;
@@ -16,7 +16,6 @@ const dynamicFeatureFlags: DynamicFeatureFlags = require('ReactFeatureFlags');
export const {
alwaysThrottleRetries,
disableDefaultPropsExceptForClasses,
disableLegacyContextForFunctionComponents,
disableSchedulerTimeoutInWorkLoop,
enableHiddenSubtreeInsertionEffectCleanup,