Compare commits

...

10 Commits

Author SHA1 Message Date
Andrew Clark
8a015b68cc Add deprecation warning for unmountComponentAtNode
This should have been deprecated in 18.0 alongside the other legacy
DOM APIs like render().
2024-04-25 12:22:01 -04:00
Andrew Clark
c3b2839641 Add deprecation warning for findDOMNode
This is removed in version 19. We already warned inside of Strict Mode
but this adds the warning everywhere.
2024-04-25 12:22:01 -04:00
Andrew Clark
d4ea75dc42 ReactDOMTestUtils deprecation warnings
Adds a deprecation warning to ReactDOMTestUtils.renderIntoDocument,
which is removed in version 19.

Also backports the deprecation warning for ReactDOMTestUtils.act.
2024-04-25 12:22:01 -04:00
Andrew Clark
9090712fd3 Support writing to this.refs from userspace
Previously, the `refs` property of a class component instance was
read-only by user code — only React could write to it, and until/unless
a string ref was used, it pointed to a shared empty object that was
frozen in dev to prevent userspace mutations.

Because string refs are deprecated, we want users to be able to codemod
all their string refs to callback refs. The safest way to do this is to
output a callback ref that assigns to `this.refs`.

So to support this, we need to make `this.refs` writable by userspace.
2024-04-25 12:21:54 -04:00
Josh Story
7548c019ce Deprecate renderToStaticNodeStream (#28872) (#28874)
This commit adds warnings indicating that `renderToStaticNodeStream`
will be removed in an upcoming React release. This API has been legacy,
is not widely used (renderToStaticMarkup is more common) and has
semantically eqiuvalent implementations with renderToReadableStream and
renderToPipeableStream.

landed in main in #28872 
changed the warning to match renderToNodeStream
2024-04-23 13:37:49 -04:00
Andrew Clark
415ee0e6ea Backport legacy context deprecation warning
This backports a deprecation warning for legacy context, even when
Strict Mode is not enabled.

I didn't bother to update all the tests because the tests are in such
a different state than what's on `main`, and on `main` we already
updated the tests accordingly. So instead I silenced the warnings in
our test config, like we've done for other warnings in the past.
2024-04-18 11:58:29 -04:00
Sebastian Markbåge
589423270e Enable warning for defaultProps on function components for everyone (#25699)
This also fixes a gap where were weren't warning on memo components.
2024-04-16 12:56:12 -04:00
Sebastian Markbåge
73bfaa16e1 Turn on key spread warning in jsx-runtime for everyone (#25697)
This improves the error message a bit and ensures that we recommend
putting the key first, not last, which ensures that the faster
`jsx-runtime` is used.

This only affects the modern "automatic" JSX transform.
2024-04-16 12:45:09 -04:00
Sebastian Silbermann
c2a246e956 Turn on string ref deprecation warning for everybody (not codemoddable) (#25383)
## Summary
 
Alternate to https://github.com/facebook/react/pull/25334 without any
prod runtime changes i.e. the proposed codemod in
https://github.com/reactjs/rfcs/blob/createlement-rfc/text/0000-create-element-changes.md#deprecate-string-refs-and-remove-production-mode-_owner-field
would not work.

## How did you test this change?

- [x] CI
- [x] `yarn test` with and without `warnAboutStringRefs`
2024-04-16 12:44:17 -04:00
Andrew Clark
2cfb4741fd Bump version from 18.2 to 18.3
We're going to use this branch to release a minor 18.3 release based off
the published 18.2 release revision. This will include some additional
warnings to assist in upgrading to React 19, but no behavior changes
compared to 18.2.

I bumped the React version to 18.3 and all the other packages by a patch
revision (since we're not going to update anything in those).
2024-04-16 12:08:51 -04:00
67 changed files with 1193 additions and 531 deletions

View File

@@ -529,21 +529,24 @@ workflows:
# - "-r=www-modern --env=production --variant=true"
# TODO: Test more persistent configurations?
- download_base_build_for_sizebot:
filters:
branches:
ignore:
- main
requires:
- setup
- sizebot:
filters:
branches:
ignore:
- main
requires:
- download_base_build_for_sizebot
- yarn_build_combined
# Sizebot is disabled because the base revision of this 18.3 branch is
# too old
# - download_base_build_for_sizebot:
# filters:
# branches:
# ignore:
# - main
# requires:
# - setup
# - sizebot:
# filters:
# branches:
# ignore:
# - main
# requires:
# - download_base_build_for_sizebot
# - yarn_build_combined
- yarn_lint_build:
requires:
- yarn_build_combined

View File

@@ -18,25 +18,25 @@
//
// 0.0.0-experimental-241c4467e-20200129
const ReactVersion = '18.2.0';
const ReactVersion = '18.3.0';
// The label used by the @next channel. Represents the upcoming release's
// stability. Could be "alpha", "beta", "rc", etc.
const nextChannelLabel = 'next';
const stablePackages = {
'eslint-plugin-react-hooks': '4.6.0',
'jest-react': '0.14.0',
'eslint-plugin-react-hooks': '4.6.1',
'jest-react': '0.14.1',
react: ReactVersion,
'react-art': ReactVersion,
'react-dom': ReactVersion,
'react-is': ReactVersion,
'react-reconciler': '0.29.0',
'react-refresh': '0.14.0',
'react-reconciler': '0.29.1',
'react-refresh': '0.14.1',
'react-test-renderer': ReactVersion,
'use-subscription': '1.8.0',
'use-sync-external-store': '1.2.0',
scheduler: '0.23.0',
'use-subscription': '1.8.1',
'use-sync-external-store': '1.2.1',
scheduler: '0.23.1',
};
// These packages do not exist in the @next or @latest channel, only

View File

@@ -20,8 +20,8 @@
"homepage": "https://reactjs.org/",
"peerDependencies": {
"jest": "^23.0.1 || ^24.0.0 || ^25.1.0",
"react": "^18.1.0",
"react-test-renderer": "^18.1.0"
"react": "^18.3.0",
"react-test-renderer": "^18.3.0"
},
"files": [
"LICENSE",

View File

@@ -1,7 +1,7 @@
{
"name": "react-art",
"description": "React ART is a JavaScript library for drawing vector graphics using React. It provides declarative and reactive bindings to the ART library. Using the same declarative API you can render the output to either Canvas, SVG or VML (IE8).",
"version": "18.1.0",
"version": "18.3.0",
"main": "index.js",
"repository": {
"type": "git",
@@ -28,7 +28,7 @@
"scheduler": "^0.22.0"
},
"peerDependencies": {
"react": "^18.1.0"
"react": "^18.3.0"
},
"files": [
"LICENSE",

View File

@@ -882,7 +882,11 @@ describe('ReactHooksInspectionIntegration', () => {
await LazyFoo;
Scheduler.unstable_flushAll();
expect(() => {
Scheduler.unstable_flushAll();
}).toErrorDev([
'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);

View File

@@ -1,6 +1,6 @@
{
"name": "react-dom",
"version": "18.1.0",
"version": "18.3.0",
"description": "React package for working with the DOM.",
"main": "index.js",
"repository": {
@@ -21,7 +21,7 @@
"scheduler": "^0.22.0"
},
"peerDependencies": {
"react": "^18.1.0"
"react": "^18.3.0"
},
"files": [
"LICENSE",

View File

@@ -12,6 +12,7 @@
let React;
let ReactDOM;
let ReactDOMServer;
let ReactFeatureFlags;
let ReactTestUtils;
describe('ReactComponent', () => {
@@ -21,6 +22,7 @@ describe('ReactComponent', () => {
React = require('react');
ReactDOM = require('react-dom');
ReactDOMServer = require('react-dom/server');
ReactFeatureFlags = require('shared/ReactFeatureFlags');
ReactTestUtils = require('react-dom/test-utils');
});
@@ -36,7 +38,7 @@ describe('ReactComponent', () => {
}).toThrowError(/Target container is not a DOM element./);
});
it('should throw when supplying a ref outside of render method', () => {
it('should throw when supplying a string ref outside of render method', () => {
let instance = <div ref="badDiv" />;
expect(function() {
instance = ReactTestUtils.renderIntoDocument(instance);
@@ -102,7 +104,7 @@ describe('ReactComponent', () => {
}
});
it('should support refs on owned components', () => {
it('should support string refs on owned components', () => {
const innerObj = {};
const outerObj = {};
@@ -133,10 +135,29 @@ describe('ReactComponent', () => {
}
}
ReactTestUtils.renderIntoDocument(<Component />);
expect(() => {
ReactTestUtils.renderIntoDocument(<Component />);
}).toErrorDev(
ReactFeatureFlags.warnAboutStringRefs
? [
'Warning: Component "div" contains the string ref "inner". ' +
'Support for string refs will be removed in a future major release. ' +
'We recommend using useRef() or createRef() instead. ' +
'Learn more about using refs safely here: https://reactjs.org/link/strict-mode-string-ref\n' +
' in div (at **)\n' +
' in Wrapper (at **)\n' +
' in Component (at **)',
'Warning: Component "Component" contains the string ref "outer". ' +
'Support for string refs will be removed in a future major release. ' +
'We recommend using useRef() or createRef() instead. ' +
'Learn more about using refs safely here: https://reactjs.org/link/strict-mode-string-ref\n' +
' in Component (at **)',
]
: [],
);
});
it('should not have refs on unmounted components', () => {
it('should not have string refs on unmounted components', () => {
class Parent extends React.Component {
render() {
return (

View File

@@ -378,7 +378,7 @@ describe('ReactComponentLifeCycle', () => {
}
// you would *NEVER* do anything like this in real code!
this.state.hasRenderCompleted = true;
return <div ref="theDiv">I am the inner DIV</div>;
return <div ref={React.createRef()}>I am the inner DIV</div>;
}
componentWillUnmount() {
@@ -477,7 +477,9 @@ describe('ReactComponentLifeCycle', () => {
class Component extends React.Component {
render() {
return (
<Tooltip ref="tooltip" tooltip={<div>{this.props.tooltipText}</div>}>
<Tooltip
ref={React.createRef()}
tooltip={<div>{this.props.tooltipText}</div>}>
{this.props.text}
</Tooltip>
);

View File

@@ -72,6 +72,8 @@ describe('ReactCompositeComponent', () => {
MorphingComponent = class extends React.Component {
state = {activated: false};
xRef = React.createRef();
_toggleActivatedState = () => {
this.setState({activated: !this.state.activated});
};
@@ -79,9 +81,9 @@ describe('ReactCompositeComponent', () => {
render() {
const toggleActivatedState = this._toggleActivatedState;
return !this.state.activated ? (
<a ref="x" onClick={toggleActivatedState} />
<a ref={this.xRef} onClick={toggleActivatedState} />
) : (
<b ref="x" onClick={toggleActivatedState} />
<b ref={this.xRef} onClick={toggleActivatedState} />
);
}
};
@@ -91,14 +93,16 @@ describe('ReactCompositeComponent', () => {
* reallocated again.
*/
ChildUpdates = class extends React.Component {
anchorRef = React.createRef();
getAnchor = () => {
return this.refs.anch;
return this.anchorRef.current;
};
render() {
const className = this.props.anchorClassOn ? 'anchorClass' : '';
return this.props.renderAnchor ? (
<a ref="anch" className={className} />
<a ref={this.anchorRef} className={className} />
) : (
<b />
);
@@ -186,11 +190,11 @@ describe('ReactCompositeComponent', () => {
it('should rewire refs when rendering to different child types', () => {
const instance = ReactTestUtils.renderIntoDocument(<MorphingComponent />);
expect(instance.refs.x.tagName).toBe('A');
expect(instance.xRef.current.tagName).toBe('A');
instance._toggleActivatedState();
expect(instance.refs.x.tagName).toBe('B');
expect(instance.xRef.current.tagName).toBe('B');
instance._toggleActivatedState();
expect(instance.refs.x.tagName).toBe('A');
expect(instance.xRef.current.tagName).toBe('A');
});
it('should not cache old DOM nodes when switching constructors', () => {
@@ -739,10 +743,13 @@ describe('ReactCompositeComponent', () => {
}
class Wrapper extends React.Component {
parentRef = React.createRef();
childRef = React.createRef();
render() {
return (
<Parent ref="parent">
<Child ref="child" />
<Parent ref={this.parentRef}>
<Child ref={this.childRef} />
</Parent>
);
}
@@ -750,14 +757,14 @@ describe('ReactCompositeComponent', () => {
const wrapper = ReactTestUtils.renderIntoDocument(<Wrapper />);
expect(wrapper.refs.parent.state.flag).toEqual(true);
expect(wrapper.refs.child.context).toEqual({flag: true});
expect(wrapper.parentRef.current.state.flag).toEqual(true);
expect(wrapper.childRef.current.context).toEqual({flag: true});
// We update <Parent /> while <Child /> is still a static prop relative to this update
wrapper.refs.parent.setState({flag: false});
wrapper.parentRef.current.setState({flag: false});
expect(wrapper.refs.parent.state.flag).toEqual(false);
expect(wrapper.refs.child.context).toEqual({flag: false});
expect(wrapper.parentRef.current.state.flag).toEqual(false);
expect(wrapper.childRef.current.context).toEqual({flag: false});
});
it('should pass context transitively', () => {
@@ -1142,14 +1149,17 @@ describe('ReactCompositeComponent', () => {
}
class Component extends React.Component {
static0Ref = React.createRef();
static1Ref = React.createRef();
render() {
if (this.props.flipped) {
return (
<div>
<Static ref="static0" key="B">
<Static ref={this.static0Ref} key="B">
B (ignored)
</Static>
<Static ref="static1" key="A">
<Static ref={this.static1Ref} key="A">
A (ignored)
</Static>
</div>
@@ -1157,10 +1167,10 @@ describe('ReactCompositeComponent', () => {
} else {
return (
<div>
<Static ref="static0" key="A">
<Static ref={this.static0Ref} key="A">
A
</Static>
<Static ref="static1" key="B">
<Static ref={this.static1Ref} key="B">
B
</Static>
</div>
@@ -1171,14 +1181,14 @@ describe('ReactCompositeComponent', () => {
const container = document.createElement('div');
const comp = ReactDOM.render(<Component flipped={false} />, container);
expect(ReactDOM.findDOMNode(comp.refs.static0).textContent).toBe('A');
expect(ReactDOM.findDOMNode(comp.refs.static1).textContent).toBe('B');
expect(ReactDOM.findDOMNode(comp.static0Ref.current).textContent).toBe('A');
expect(ReactDOM.findDOMNode(comp.static1Ref.current).textContent).toBe('B');
// When flipping the order, the refs should update even though the actual
// contents do not
ReactDOM.render(<Component flipped={true} />, container);
expect(ReactDOM.findDOMNode(comp.refs.static0).textContent).toBe('B');
expect(ReactDOM.findDOMNode(comp.refs.static1).textContent).toBe('A');
expect(ReactDOM.findDOMNode(comp.static0Ref.current).textContent).toBe('B');
expect(ReactDOM.findDOMNode(comp.static1Ref.current).textContent).toBe('A');
});
it('should allow access to findDOMNode in componentWillUnmount', () => {
@@ -1453,10 +1463,11 @@ describe('ReactCompositeComponent', () => {
this.state = {
color: 'green',
};
this.appleRef = React.createRef();
}
render() {
return <Apple color={this.state.color} ref="apple" />;
return <Apple color={this.state.color} ref={this.appleRef} />;
}
}
@@ -1502,15 +1513,15 @@ describe('ReactCompositeComponent', () => {
expect(renderCalls).toBe(2);
// Re-render base on state
instance.refs.apple.cut();
instance.appleRef.current.cut();
expect(renderCalls).toBe(3);
// No re-render based on state
instance.refs.apple.cut();
instance.appleRef.current.cut();
expect(renderCalls).toBe(3);
// Re-render based on state again
instance.refs.apple.eatSlice();
instance.appleRef.current.eatSlice();
expect(renderCalls).toBe(4);
});

View File

@@ -193,12 +193,13 @@ describe('ReactDOMEventListener', () => {
const onMouseOut = event => mouseOut(event.target);
class Wrapper extends React.Component {
innerRef = React.createRef();
getInner = () => {
return this.refs.inner;
return this.innerRef.current;
};
render() {
const inner = <div ref="inner">Inner</div>;
const inner = <div ref={this.innerRef}>Inner</div>;
return (
<div>
<div onMouseOut={onMouseOut} id="outer">

View File

@@ -264,6 +264,7 @@ describe('ReactDOMFizzServer', () => {
}
// @gate experimental
// @gate !warnAboutDefaultPropsOnFunctionComponents || !__DEV__
it('should asynchronously load a lazy component', async () => {
let resolveA;
const LazyA = React.lazy(() => {

View File

@@ -16,6 +16,8 @@ let usingPartialRenderer;
const util = require('util');
const realConsoleError = console.error;
const shouldIgnoreConsoleError = require('../../../../scripts/jest/shouldIgnoreConsoleError');
describe('ReactDOMServerHydration', () => {
let container;
@@ -57,6 +59,9 @@ describe('ReactDOMServerHydration', () => {
// We only want console errors in this suite.
return null;
}
if (shouldIgnoreConsoleError(format, ...rest)) {
return null;
}
rest[rest.length - 1] = normalizeCodeLocInfo(rest[rest.length - 1]);
return util.format(format, ...rest);
}

View File

@@ -1071,22 +1071,31 @@ describe('ReactDOMInput', () => {
it('should control radio buttons', () => {
class RadioGroup extends React.Component {
aRef = React.createRef();
bRef = React.createRef();
cRef = React.createRef();
render() {
return (
<div>
<input
ref="a"
ref={this.aRef}
type="radio"
name="fruit"
checked={true}
onChange={emptyFunction}
/>
A
<input ref="b" type="radio" name="fruit" onChange={emptyFunction} />
<input
ref={this.bRef}
type="radio"
name="fruit"
onChange={emptyFunction}
/>
B
<form>
<input
ref="c"
ref={this.cRef}
type="radio"
name="fruit"
defaultChecked={true}
@@ -1099,9 +1108,9 @@ describe('ReactDOMInput', () => {
}
const stub = ReactDOM.render(<RadioGroup />, container);
const aNode = stub.refs.a;
const bNode = stub.refs.b;
const cNode = stub.refs.c;
const aNode = stub.aRef.current;
const bNode = stub.bRef.current;
const cNode = stub.cRef.current;
expect(aNode.checked).toBe(true);
expect(bNode.checked).toBe(false);

View File

@@ -337,7 +337,7 @@ describe('ReactDOMServerIntegration', () => {
itRenders('no ref attribute', async render => {
class RefComponent extends React.Component {
render() {
return <div ref="foo" />;
return <div ref={React.createRef()} />;
}
}
const e = await render(<RefComponent />);

View File

@@ -14,6 +14,7 @@ const ReactDOMServerIntegrationUtils = require('./utils/ReactDOMServerIntegratio
let React;
let ReactDOM;
let ReactDOMServer;
let ReactFeatureFlags;
let ReactTestUtils;
function initModules() {
@@ -22,6 +23,7 @@ function initModules() {
React = require('react');
ReactDOM = require('react-dom');
ReactDOMServer = require('react-dom/server');
ReactFeatureFlags = require('shared/ReactFeatureFlags');
ReactTestUtils = require('react-dom/test-utils');
// Make them available to the helpers.
@@ -91,10 +93,22 @@ describe('ReactDOMServerIntegration', () => {
root.innerHTML = markup;
let component = null;
resetModules();
await asyncReactDOMRender(
<RefsComponent ref={e => (component = e)} />,
root,
true,
await expect(async () => {
await asyncReactDOMRender(
<RefsComponent ref={e => (component = e)} />,
root,
true,
);
}).toErrorDev(
ReactFeatureFlags.warnAboutStringRefs
? [
'Warning: Component "RefsComponent" contains the string ref "myDiv". ' +
'Support for string refs will be removed in a future major release. ' +
'We recommend using useRef() or createRef() instead. ' +
'Learn more about using refs safely here: https://reactjs.org/link/strict-mode-string-ref\n' +
' in RefsComponent (at **)',
]
: [],
);
expect(component.refs.myDiv).toBe(root.firstChild);
});

View File

@@ -10,7 +10,6 @@
'use strict';
let React;
let ReactFeatureFlags;
let ReactNoop;
let Scheduler;
let JSXDEVRuntime;
@@ -19,19 +18,11 @@ describe('ReactDeprecationWarnings', () => {
beforeEach(() => {
jest.resetModules();
React = require('react');
ReactFeatureFlags = require('shared/ReactFeatureFlags');
ReactNoop = require('react-noop-renderer');
Scheduler = require('scheduler');
if (__DEV__) {
JSXDEVRuntime = require('react/jsx-dev-runtime');
}
ReactFeatureFlags.warnAboutDefaultPropsOnFunctionComponents = true;
ReactFeatureFlags.warnAboutStringRefs = true;
});
afterEach(() => {
ReactFeatureFlags.warnAboutDefaultPropsOnFunctionComponents = false;
ReactFeatureFlags.warnAboutStringRefs = false;
});
it('should warn when given defaultProps', () => {
@@ -51,6 +42,27 @@ describe('ReactDeprecationWarnings', () => {
);
});
it('should warn when given defaultProps on a memoized function', () => {
const MemoComponent = React.memo(function FunctionalComponent(props) {
return null;
});
MemoComponent.defaultProps = {
testProp: true,
};
ReactNoop.render(
<div>
<MemoComponent />
</div>,
);
expect(() => expect(Scheduler).toFlushWithoutYielding()).toErrorDev(
'Warning: FunctionalComponent: Support for defaultProps ' +
'will be removed from memo components in a future major ' +
'release. Use JavaScript default parameters instead.',
);
});
it('should warn when given string refs', () => {
class RefComponent extends React.Component {
render() {
@@ -74,9 +86,7 @@ describe('ReactDeprecationWarnings', () => {
);
});
it('should not warn when owner and self are the same for string refs', () => {
ReactFeatureFlags.warnAboutStringRefs = false;
it('should warn when owner and self are the same for string refs', () => {
class RefComponent extends React.Component {
render() {
return null;
@@ -87,7 +97,11 @@ describe('ReactDeprecationWarnings', () => {
return <RefComponent ref="refComponent" __self={this} />;
}
}
ReactNoop.renderLegacySyncRoot(<Component />);
expect(() => {
ReactNoop.renderLegacySyncRoot(<Component />);
}).toErrorDev([
'Component "Component" contains the string ref "refComponent". Support for string refs will be removed in a future major release.',
]);
expect(Scheduler).toFlushWithoutYielding();
});

View File

@@ -367,11 +367,12 @@ describe('ReactFunctionComponent', () => {
Child.defaultProps = {test: 2};
Child.propTypes = {test: PropTypes.string};
expect(() => ReactTestUtils.renderIntoDocument(<Child />)).toErrorDev(
expect(() => ReactTestUtils.renderIntoDocument(<Child />)).toErrorDev([
'Warning: Child: Support for defaultProps will be removed from function components in a future major release. Use JavaScript default parameters instead.',
'Warning: Failed prop type: Invalid prop `test` of type `number` ' +
'supplied to `Child`, expected `string`.\n' +
' in Child (at **)',
);
]);
});
it('should receive context', () => {

View File

@@ -67,17 +67,18 @@ describe('ReactIdentity', () => {
function renderAComponentWithKeyIntoContainer(key, container) {
class Wrapper extends React.Component {
spanRef = React.createRef();
render() {
return (
<div>
<span ref="span" key={key} />
<span ref={this.spanRef} key={key} />
</div>
);
}
}
const instance = ReactDOM.render(<Wrapper />, container);
const span = instance.refs.span;
const span = instance.spanRef.current;
expect(span).not.toBe(null);
}

View File

@@ -60,6 +60,8 @@ class StatusDisplay extends React.Component {
* Displays friends statuses.
*/
class FriendsStatusDisplay extends React.Component {
displays = {};
/**
* Gets the order directly from each rendered child's `index` field.
* Refs are not maintained in the rendered order, and neither is
@@ -84,7 +86,7 @@ class FriendsStatusDisplay extends React.Component {
const originalKeys = this.getOriginalKeys();
for (let i = 0; i < originalKeys.length; i++) {
const key = originalKeys[i];
res[key] = this.refs[key];
res[key] = this.displays[key];
}
return res;
}
@@ -104,7 +106,7 @@ class FriendsStatusDisplay extends React.Component {
// We are only interested in children up to the current key.
return;
}
expect(this.refs[key]).toBeTruthy();
expect(this.displays[key]).toBeTruthy();
}
}
@@ -116,7 +118,9 @@ class FriendsStatusDisplay extends React.Component {
!status ? null : (
<StatusDisplay
key={key}
ref={key}
ref={current => {
this.displays[key] = current;
}}
contentKey={key}
onFlush={this.verifyPreviousRefsResolved.bind(this, key)}
status={status}

View File

@@ -620,17 +620,27 @@ describe('ReactDOMServer', () => {
describe('renderToStaticNodeStream', () => {
it('should generate simple markup', () => {
const SuccessfulElement = React.createElement(() => <img />);
const response = ReactDOMServer.renderToStaticNodeStream(
SuccessfulElement,
);
expect(response.read().toString()).toMatch(new RegExp('<img' + '/>'));
expect(() => {
const response = ReactDOMServer.renderToStaticNodeStream(
SuccessfulElement,
);
expect(response.read().toString()).toMatch(new RegExp('<img' + '/>'));
}).toErrorDev('ReactDOMServer.renderToStaticNodeStream() is deprecated', {
withoutStack: true,
});
});
it('should handle errors correctly', () => {
const FailingElement = React.createElement(() => {
throw new Error('An Error');
});
const response = ReactDOMServer.renderToStaticNodeStream(FailingElement);
let response;
expect(() => {
response = ReactDOMServer.renderToStaticNodeStream(FailingElement);
}).toErrorDev('ReactDOMServer.renderToStaticNodeStream() is deprecated', {
withoutStack: true,
});
return new Promise(resolve => {
response.once('error', () => {
resolve();

View File

@@ -32,6 +32,8 @@ describe('ReactDOMServerHydration', () => {
let numClicks = 0;
class TestComponent extends React.Component {
spanRef = React.createRef();
componentDidMount() {
mountCount++;
}
@@ -42,7 +44,7 @@ describe('ReactDOMServerHydration', () => {
render() {
return (
<span ref="span" onClick={this.click}>
<span ref={this.spanRef} onClick={this.click}>
Name: {this.props.name}
</span>
);
@@ -85,7 +87,7 @@ describe('ReactDOMServerHydration', () => {
// Ensure the events system works after mount into server markup
expect(numClicks).toEqual(0);
instance.refs.span.click();
instance.spanRef.current.click();
expect(numClicks).toEqual(1);
ReactDOM.unmountComponentAtNode(element);
@@ -103,7 +105,7 @@ describe('ReactDOMServerHydration', () => {
// Ensure the events system works after markup mismatch.
expect(numClicks).toEqual(1);
instance.refs.span.click();
instance.spanRef.current.click();
expect(numClicks).toEqual(2);
} finally {
document.body.removeChild(element);

View File

@@ -222,13 +222,17 @@ describe('ReactTestUtils', () => {
// Full-page components (html, head, body) can't be rendered into a div
// directly...
class Root extends React.Component {
htmlRef = React.createRef();
headRef = React.createRef();
bodyRef = React.createRef();
render() {
return (
<html ref="html">
<head ref="head">
<html ref={this.htmlRef}>
<head ref={this.headRef}>
<title>hello</title>
</head>
<body ref="body">hello, world</body>
<body ref={this.bodyRef}>hello, world</body>
</html>
);
}
@@ -238,12 +242,12 @@ describe('ReactTestUtils', () => {
const testDocument = getTestDocument(markup);
const component = ReactDOM.hydrate(<Root />, testDocument);
expect(component.refs.html.tagName).toBe('HTML');
expect(component.refs.head.tagName).toBe('HEAD');
expect(component.refs.body.tagName).toBe('BODY');
expect(ReactTestUtils.isDOMComponent(component.refs.html)).toBe(true);
expect(ReactTestUtils.isDOMComponent(component.refs.head)).toBe(true);
expect(ReactTestUtils.isDOMComponent(component.refs.body)).toBe(true);
expect(component.htmlRef.current.tagName).toBe('HTML');
expect(component.headRef.current.tagName).toBe('HEAD');
expect(component.bodyRef.current.tagName).toBe('BODY');
expect(ReactTestUtils.isDOMComponent(component.htmlRef.current)).toBe(true);
expect(ReactTestUtils.isDOMComponent(component.headRef.current)).toBe(true);
expect(ReactTestUtils.isDOMComponent(component.bodyRef.current)).toBe(true);
});
it('can scry with stateless components involved', () => {
@@ -349,12 +353,13 @@ describe('ReactTestUtils', () => {
it('should change the value of an input field in a component', () => {
class SomeComponent extends React.Component {
inputRef = React.createRef();
render() {
return (
<div>
<input
type="text"
ref="input"
ref={this.inputRef}
onChange={this.props.handleChange}
/>
</div>
@@ -374,7 +379,7 @@ describe('ReactTestUtils', () => {
container,
);
const node = instance.refs.input;
const node = instance.inputRef.current;
node.value = 'zebra';
ReactTestUtils.Simulate.change(node);

View File

@@ -493,8 +493,11 @@ function runActTests(label, render, unmount, rerender) {
// it's annoying that we have to wait a tick before this warning comes in
await sleep(0);
if (__DEV__) {
expect(console.error.calls.count()).toEqual(1);
expect(console.error.calls.count()).toEqual(2);
expect(console.error.calls.argsFor(0)[0]).toMatch(
'`ReactDOMTestUtils.act` is deprecated ',
);
expect(console.error.calls.argsFor(1)[0]).toMatch(
'You called act(async () => ...) without await.',
);
}
@@ -516,13 +519,16 @@ function runActTests(label, render, unmount, rerender) {
await sleep(150);
if (__DEV__) {
expect(console.error).toHaveBeenCalledTimes(2);
expect(console.error).toHaveBeenCalledTimes(3);
expect(console.error.calls.argsFor(0)[0]).toMatch(
'You seem to have overlapping act() calls',
'`ReactDOMTestUtils.act` is deprecated ',
);
expect(console.error.calls.argsFor(1)[0]).toMatch(
'You seem to have overlapping act() calls',
);
expect(console.error.calls.argsFor(2)[0]).toMatch(
'You seem to have overlapping act() calls',
);
}
});

View File

@@ -147,6 +147,7 @@ describe('ReactUpdates', () => {
class Parent extends React.Component {
state = {x: 0};
childRef = React.createRef();
componentDidUpdate() {
parentUpdateCount++;
@@ -155,7 +156,7 @@ describe('ReactUpdates', () => {
render() {
return (
<div>
<Child ref="child" x={this.state.x} />
<Child ref={this.childRef} x={this.state.x} />
</div>
);
}
@@ -176,7 +177,7 @@ describe('ReactUpdates', () => {
}
const instance = ReactTestUtils.renderIntoDocument(<Parent />);
const child = instance.refs.child;
const child = instance.childRef.current;
expect(instance.state.x).toBe(0);
expect(child.state.y).toBe(0);
@@ -200,6 +201,7 @@ describe('ReactUpdates', () => {
class Parent extends React.Component {
state = {x: 0};
childRef = React.createRef();
componentDidUpdate() {
parentUpdateCount++;
@@ -208,7 +210,7 @@ describe('ReactUpdates', () => {
render() {
return (
<div>
<Child ref="child" x={this.state.x} />
<Child ref={this.childRef} x={this.state.x} />
</div>
);
}
@@ -229,7 +231,7 @@ describe('ReactUpdates', () => {
}
const instance = ReactTestUtils.renderIntoDocument(<Parent />);
const child = instance.refs.child;
const child = instance.childRef.current;
expect(instance.state.x).toBe(0);
expect(child.state.y).toBe(0);
@@ -336,13 +338,15 @@ describe('ReactUpdates', () => {
let childRenderCount = 0;
class Parent extends React.Component {
childRef = React.createRef();
shouldComponentUpdate() {
return false;
}
render() {
parentRenderCount++;
return <Child ref="child" />;
return <Child ref={this.childRef} />;
}
}
@@ -370,7 +374,7 @@ describe('ReactUpdates', () => {
expect(childRenderCount).toBe(1);
ReactDOM.unstable_batchedUpdates(function() {
instance.refs.child.setState({x: 1});
instance.childRef.current.setState({x: 1});
});
expect(parentRenderCount).toBe(1);
@@ -428,28 +432,34 @@ describe('ReactUpdates', () => {
};
class Box extends React.Component {
boxDivRef = React.createRef();
render() {
return <div ref="boxDiv">{this.props.children}</div>;
return <div ref={this.boxDivRef}>{this.props.children}</div>;
}
}
Object.assign(Box.prototype, UpdateLoggingMixin);
class Child extends React.Component {
spanRef = React.createRef();
render() {
return <span ref="span">child</span>;
return <span ref={this.spanRef}>child</span>;
}
}
Object.assign(Child.prototype, UpdateLoggingMixin);
class Switcher extends React.Component {
state = {tabKey: 'hello'};
boxRef = React.createRef();
switcherDivRef = React.createRef();
render() {
const child = this.props.children;
return (
<Box ref="box">
<Box ref={this.boxRef}>
<div
ref="switcherDiv"
ref={this.switcherDivRef}
style={{
display: this.state.tabKey === child.key ? '' : 'none',
}}>
@@ -462,10 +472,13 @@ describe('ReactUpdates', () => {
Object.assign(Switcher.prototype, UpdateLoggingMixin);
class App extends React.Component {
switcherRef = React.createRef();
childRef = React.createRef();
render() {
return (
<Switcher ref="switcher">
<Child key="hello" ref="child" />
<Switcher ref={this.switcherRef}>
<Child key="hello" ref={this.childRef} />
</Switcher>
);
}
@@ -513,21 +526,21 @@ describe('ReactUpdates', () => {
expectUpdates(desiredWillUpdates, desiredDidUpdates);
}
testUpdates(
[root.refs.switcher.refs.box, root.refs.switcher],
[root.switcherRef.current.boxRef.current, root.switcherRef.current],
// Owner-child relationships have inverse will and did
['Switcher', 'Box'],
['Box', 'Switcher'],
);
testUpdates(
[root.refs.child, root.refs.switcher.refs.box],
[root.childRef.current, root.switcherRef.current.boxRef.current],
// Not owner-child so reconcile independently
['Box', 'Child'],
['Box', 'Child'],
);
testUpdates(
[root.refs.child, root.refs.switcher],
[root.childRef.current, root.switcherRef.current],
// Switcher owns Box and Child, Box does not own Child
['Switcher', 'Box', 'Child'],
['Box', 'Switcher', 'Child'],
@@ -588,12 +601,13 @@ describe('ReactUpdates', () => {
class Outer extends React.Component {
state = {x: 0};
innerRef = React.createRef();
render() {
updates.push('Outer-render-' + this.state.x);
return (
<div>
<Inner x={this.state.x} ref="inner" />
<Inner x={this.state.x} ref={this.innerRef} />
</div>
);
}
@@ -602,7 +616,7 @@ describe('ReactUpdates', () => {
const x = this.state.x;
updates.push('Outer-didUpdate-' + x);
updates.push('Inner-setState-' + x);
this.refs.inner.setState({x: x}, function() {
this.innerRef.current.setState({x: x}, function() {
updates.push('Inner-callback-' + x);
});
}
@@ -945,12 +959,14 @@ describe('ReactUpdates', () => {
it('does not update one component twice in a batch (#2410)', () => {
class Parent extends React.Component {
childRef = React.createRef();
getChild = () => {
return this.refs.child;
return this.childRef.current;
};
render() {
return <Child ref="child" />;
return <Child ref={this.childRef} />;
}
}

View File

@@ -30,6 +30,9 @@ describe('refs-destruction', () => {
}
TestComponent = class extends React.Component {
theInnerDivRef = React.createRef();
theInnerClassComponentRef = React.createRef();
render() {
if (this.props.destroy) {
return <div />;
@@ -43,8 +46,8 @@ describe('refs-destruction', () => {
} else {
return (
<div>
<div ref="theInnerDiv" />
<ClassComponent ref="theInnerClassComponent" />
<div ref={this.theInnerDivRef} />
<ClassComponent ref={this.theInnerClassComponentRef} />
</div>
);
}
@@ -55,52 +58,45 @@ describe('refs-destruction', () => {
it('should remove refs when destroying the parent', () => {
const container = document.createElement('div');
const testInstance = ReactDOM.render(<TestComponent />, container);
expect(ReactTestUtils.isDOMComponent(testInstance.refs.theInnerDiv)).toBe(
true,
);
expect(
Object.keys(testInstance.refs || {}).filter(key => testInstance.refs[key])
.length,
).toEqual(2);
ReactTestUtils.isDOMComponent(testInstance.theInnerDivRef.current),
).toBe(true);
expect(testInstance.theInnerClassComponentRef.current).toBeTruthy();
ReactDOM.unmountComponentAtNode(container);
expect(
Object.keys(testInstance.refs || {}).filter(key => testInstance.refs[key])
.length,
).toEqual(0);
expect(testInstance.theInnerDivRef.current).toBe(null);
expect(testInstance.theInnerClassComponentRef.current).toBe(null);
});
it('should remove refs when destroying the child', () => {
const container = document.createElement('div');
const testInstance = ReactDOM.render(<TestComponent />, container);
expect(ReactTestUtils.isDOMComponent(testInstance.refs.theInnerDiv)).toBe(
true,
);
expect(
Object.keys(testInstance.refs || {}).filter(key => testInstance.refs[key])
.length,
).toEqual(2);
ReactTestUtils.isDOMComponent(testInstance.theInnerDivRef.current),
).toBe(true);
expect(testInstance.theInnerClassComponentRef.current).toBeTruthy();
ReactDOM.render(<TestComponent destroy={true} />, container);
expect(
Object.keys(testInstance.refs || {}).filter(key => testInstance.refs[key])
.length,
).toEqual(0);
expect(testInstance.theInnerDivRef.current).toBe(null);
expect(testInstance.theInnerClassComponentRef.current).toBe(null);
});
it('should remove refs when removing the child ref attribute', () => {
const container = document.createElement('div');
const testInstance = ReactDOM.render(<TestComponent />, container);
expect(ReactTestUtils.isDOMComponent(testInstance.refs.theInnerDiv)).toBe(
true,
);
expect(
Object.keys(testInstance.refs || {}).filter(key => testInstance.refs[key])
.length,
).toEqual(2);
ReactTestUtils.isDOMComponent(testInstance.theInnerDivRef.current),
).toBe(true);
expect(testInstance.theInnerClassComponentRef.current).toBeTruthy();
ReactDOM.render(<TestComponent removeRef={true} />, container);
expect(
Object.keys(testInstance.refs || {}).filter(key => testInstance.refs[key])
.length,
).toEqual(0);
expect(testInstance.theInnerDivRef.current).toBe(null);
expect(testInstance.theInnerClassComponentRef.current).toBe(null);
});
it('should not error when destroying child with ref asynchronously', () => {
@@ -135,7 +131,7 @@ describe('refs-destruction', () => {
render() {
return (
<Modal>
<a ref="ref" />
<a ref={React.createRef()} />
</Modal>
);
}

View File

@@ -11,86 +11,12 @@
let React = require('react');
let ReactDOM = require('react-dom');
let ReactFeatureFlags = require('shared/ReactFeatureFlags');
let ReactTestUtils = require('react-dom/test-utils');
/**
* Counts clicks and has a renders an item for each click. Each item rendered
* has a ref of the form "clickLogN".
*/
class ClickCounter extends React.Component {
state = {count: this.props.initialCount};
triggerReset = () => {
this.setState({count: this.props.initialCount});
};
handleClick = () => {
this.setState({count: this.state.count + 1});
};
render() {
const children = [];
let i;
for (i = 0; i < this.state.count; i++) {
children.push(
<div
className="clickLogDiv"
key={'clickLog' + i}
ref={'clickLog' + i}
/>,
);
}
return (
<span className="clickIncrementer" onClick={this.handleClick}>
{children}
</span>
);
}
}
/**
* Only purpose is to test that refs are tracked even when applied to a
* component that is injected down several layers. Ref systems are difficult to
* build in such a way that ownership is maintained in an airtight manner.
*/
class GeneralContainerComponent extends React.Component {
render() {
return <div>{this.props.children}</div>;
}
}
/**
* Notice how refs ownership is maintained even when injecting a component
* into a different parent.
*/
class TestRefsComponent extends React.Component {
doReset = () => {
this.refs.myCounter.triggerReset();
};
render() {
return (
<div>
<div ref="resetDiv" onClick={this.doReset}>
Reset Me By Clicking This.
</div>
<GeneralContainerComponent ref="myContainer">
<ClickCounter ref="myCounter" initialCount={1} />
</GeneralContainerComponent>
</div>
);
}
}
const expectClickLogsLengthToBe = function(instance, length) {
const clickLogs = ReactTestUtils.scryRenderedDOMComponentsWithClass(
instance,
'clickLogDiv',
);
expect(clickLogs.length).toBe(length);
expect(Object.keys(instance.refs.myCounter.refs).length).toBe(length);
};
// This is testing if string refs are deleted from `instance.refs`
// Once support for string refs is removed, this test can be removed.
// Detaching is already tested in refs-detruction-test.js
describe('reactiverefs', () => {
let container;
@@ -98,6 +24,7 @@ describe('reactiverefs', () => {
jest.resetModules();
React = require('react');
ReactDOM = require('react-dom');
ReactFeatureFlags = require('shared/ReactFeatureFlags');
ReactTestUtils = require('react-dom/test-utils');
});
@@ -108,13 +35,117 @@ describe('reactiverefs', () => {
}
});
/**
* Counts clicks and has a renders an item for each click. Each item rendered
* has a ref of the form "clickLogN".
*/
class ClickCounter extends React.Component {
state = {count: this.props.initialCount};
triggerReset = () => {
this.setState({count: this.props.initialCount});
};
handleClick = () => {
this.setState({count: this.state.count + 1});
};
render() {
const children = [];
let i;
for (i = 0; i < this.state.count; i++) {
children.push(
<div
className="clickLogDiv"
key={'clickLog' + i}
ref={'clickLog' + i}
/>,
);
}
return (
<span className="clickIncrementer" onClick={this.handleClick}>
{children}
</span>
);
}
}
const expectClickLogsLengthToBe = function(instance, length) {
const clickLogs = ReactTestUtils.scryRenderedDOMComponentsWithClass(
instance,
'clickLogDiv',
);
expect(clickLogs.length).toBe(length);
expect(Object.keys(instance.refs.myCounter.refs).length).toBe(length);
};
/**
* Render a TestRefsComponent and ensure that the main refs are wired up.
*/
const renderTestRefsComponent = function() {
/**
* Only purpose is to test that refs are tracked even when applied to a
* component that is injected down several layers. Ref systems are difficult to
* build in such a way that ownership is maintained in an airtight manner.
*/
class GeneralContainerComponent extends React.Component {
render() {
return <div>{this.props.children}</div>;
}
}
/**
* Notice how refs ownership is maintained even when injecting a component
* into a different parent.
*/
class TestRefsComponent extends React.Component {
doReset = () => {
this.refs.myCounter.triggerReset();
};
render() {
return (
<div>
<div ref="resetDiv" onClick={this.doReset}>
Reset Me By Clicking This.
</div>
<GeneralContainerComponent ref="myContainer">
<ClickCounter ref="myCounter" initialCount={1} />
</GeneralContainerComponent>
</div>
);
}
}
container = document.createElement('div');
document.body.appendChild(container);
const testRefsComponent = ReactDOM.render(<TestRefsComponent />, container);
let testRefsComponent;
expect(() => {
testRefsComponent = ReactDOM.render(<TestRefsComponent />, container);
}).toErrorDev(
ReactFeatureFlags.warnAboutStringRefs
? [
'Warning: Component "div" contains the string ref "resetDiv". ' +
'Support for string refs will be removed in a future major release. ' +
'We recommend using useRef() or createRef() instead. ' +
'Learn more about using refs safely here: https://reactjs.org/link/strict-mode-string-ref\n' +
' in div (at **)\n' +
' in TestRefsComponent (at **)',
'Warning: Component "span" contains the string ref "clickLog0". ' +
'Support for string refs will be removed in a future major release. ' +
'We recommend using useRef() or createRef() instead. ' +
'Learn more about using refs safely here: https://reactjs.org/link/strict-mode-string-ref\n' +
' in span (at **)\n' +
' in ClickCounter (at **)\n' +
' in div (at **)\n' +
' in GeneralContainerComponent (at **)\n' +
' in div (at **)\n' +
' in TestRefsComponent (at **)',
]
: [],
);
expect(testRefsComponent instanceof TestRefsComponent).toBe(true);
const generalContainer = testRefsComponent.refs.myContainer;
@@ -156,13 +187,14 @@ describe('reactiverefs', () => {
});
});
if (!require('shared/ReactFeatureFlags').disableModulePatternComponents) {
if (!ReactFeatureFlags.disableModulePatternComponents) {
describe('factory components', () => {
it('Should correctly get the ref', () => {
function Comp() {
return {
elemRef: React.createRef(),
render() {
return <div ref="elemRef" />;
return <div ref={this.elemRef} />;
},
};
}
@@ -177,7 +209,7 @@ if (!require('shared/ReactFeatureFlags').disableModulePatternComponents) {
'`Comp.prototype = React.Component.prototype`. ' +
"Don't use an arrow function since it cannot be called with `new` by React.",
);
expect(inst.refs.elemRef.tagName).toBe('DIV');
expect(inst.elemRef.current.tagName).toBe('DIV');
});
});
}
@@ -191,10 +223,15 @@ describe('ref swapping', () => {
jest.resetModules();
React = require('react');
ReactDOM = require('react-dom');
ReactFeatureFlags = require('shared/ReactFeatureFlags');
ReactTestUtils = require('react-dom/test-utils');
RefHopsAround = class extends React.Component {
state = {count: 0};
hopRef = React.createRef();
divOneRef = React.createRef();
divTwoRef = React.createRef();
divThreeRef = React.createRef();
moveRef = () => {
this.setState({count: this.state.count + 1});
@@ -212,15 +249,15 @@ describe('ref swapping', () => {
<div>
<div
className="first"
ref={count % 3 === 0 ? 'hopRef' : 'divOneRef'}
ref={count % 3 === 0 ? this.hopRef : this.divOneRef}
/>
<div
className="second"
ref={count % 3 === 1 ? 'hopRef' : 'divTwoRef'}
ref={count % 3 === 1 ? this.hopRef : this.divTwoRef}
/>
<div
className="third"
ref={count % 3 === 2 ? 'hopRef' : 'divThreeRef'}
ref={count % 3 === 2 ? this.hopRef : this.divThreeRef}
/>
</div>
);
@@ -244,28 +281,28 @@ describe('ref swapping', () => {
'third',
);
expect(refHopsAround.refs.hopRef).toEqual(firstDiv);
expect(refHopsAround.refs.divTwoRef).toEqual(secondDiv);
expect(refHopsAround.refs.divThreeRef).toEqual(thirdDiv);
expect(refHopsAround.hopRef.current).toEqual(firstDiv);
expect(refHopsAround.divTwoRef.current).toEqual(secondDiv);
expect(refHopsAround.divThreeRef.current).toEqual(thirdDiv);
refHopsAround.moveRef();
expect(refHopsAround.refs.divOneRef).toEqual(firstDiv);
expect(refHopsAround.refs.hopRef).toEqual(secondDiv);
expect(refHopsAround.refs.divThreeRef).toEqual(thirdDiv);
expect(refHopsAround.divOneRef.current).toEqual(firstDiv);
expect(refHopsAround.hopRef.current).toEqual(secondDiv);
expect(refHopsAround.divThreeRef.current).toEqual(thirdDiv);
refHopsAround.moveRef();
expect(refHopsAround.refs.divOneRef).toEqual(firstDiv);
expect(refHopsAround.refs.divTwoRef).toEqual(secondDiv);
expect(refHopsAround.refs.hopRef).toEqual(thirdDiv);
expect(refHopsAround.divOneRef.current).toEqual(firstDiv);
expect(refHopsAround.divTwoRef.current).toEqual(secondDiv);
expect(refHopsAround.hopRef.current).toEqual(thirdDiv);
/**
* Make sure that after the third, we're back to where we started and the
* refs are completely restored.
*/
refHopsAround.moveRef();
expect(refHopsAround.refs.hopRef).toEqual(firstDiv);
expect(refHopsAround.refs.divTwoRef).toEqual(secondDiv);
expect(refHopsAround.refs.divThreeRef).toEqual(thirdDiv);
expect(refHopsAround.hopRef.current).toEqual(firstDiv);
expect(refHopsAround.divTwoRef.current).toEqual(secondDiv);
expect(refHopsAround.divThreeRef.current).toEqual(thirdDiv);
});
it('always has a value for this.refs', () => {
@@ -309,7 +346,20 @@ describe('ref swapping', () => {
return <div ref={1} />;
}
}
const a = ReactTestUtils.renderIntoDocument(<A />);
let a;
expect(() => {
a = ReactTestUtils.renderIntoDocument(<A />);
}).toErrorDev(
ReactFeatureFlags.warnAboutStringRefs
? [
'Warning: Component "A" contains the string ref "1". ' +
'Support for string refs will be removed in a future major release. ' +
'We recommend using useRef() or createRef() instead. ' +
'Learn more about using refs safely here: https://reactjs.org/link/strict-mode-string-ref\n' +
' in A (at **)',
]
: [],
);
expect(a.refs[1].nodeName).toBe('DIV');
});
@@ -464,7 +514,7 @@ describe('root level refs', () => {
});
});
describe('creating element with ref in constructor', () => {
describe('creating element with string ref in constructor', () => {
class RefTest extends React.Component {
constructor(props) {
super(props);
@@ -521,13 +571,41 @@ describe('strings refs across renderers', () => {
const div1 = document.createElement('div');
const div2 = document.createElement('div');
const inst = ReactDOM.render(<Parent />, div1);
let inst;
expect(() => {
inst = ReactDOM.render(<Parent />, div1);
}).toErrorDev(
ReactFeatureFlags.warnAboutStringRefs
? [
'Warning: Component "Indirection" contains the string ref "child1". ' +
'Support for string refs will be removed in a future major release. ' +
'We recommend using useRef() or createRef() instead. ' +
'Learn more about using refs safely here: https://reactjs.org/link/strict-mode-string-ref\n' +
' in Indirection (at **)\n' +
' in Parent (at **)',
]
: [],
);
// Only the first ref has rendered yet.
expect(inst.refs.child1.tagName).toBe('DIV');
expect(inst.refs.child1).toBe(div1.firstChild);
// Now both refs should be rendered.
ReactDOM.render(<Parent />, div1);
expect(() => {
// Now both refs should be rendered.
ReactDOM.render(<Parent />, div1);
}).toErrorDev(
ReactFeatureFlags.warnAboutStringRefs
? [
'Warning: Component "Root" contains the string ref "child2". ' +
'Support for string refs will be removed in a future major release. ' +
'We recommend using useRef() or createRef() instead. ' +
'Learn more about using refs safely here: https://reactjs.org/link/strict-mode-string-ref',
]
: [],
{withoutStack: true},
);
expect(inst.refs.child1.tagName).toBe('DIV');
expect(inst.refs.child1).toBe(div1.firstChild);
expect(inst.refs.child2.tagName).toBe('DIV');

View File

@@ -238,10 +238,22 @@ function legacyRenderSubtreeIntoContainer(
return getPublicRootInstance(root);
}
let didWarnAboutFindDOMNode = false;
export function findDOMNode(
componentOrElement: Element | ?React$Component<any, any>,
): null | Element | Text {
if (__DEV__) {
if (!didWarnAboutFindDOMNode) {
didWarnAboutFindDOMNode = true;
console.error(
'findDOMNode is deprecated and will be removed in the next major ' +
'release. Instead, add a ref directly to the element you want ' +
'to reference. Learn more about using refs safely here: ' +
'https://reactjs.org/link/strict-mode-find-node',
);
}
const owner = (ReactCurrentOwner.current: any);
if (owner !== null && owner.stateNode !== null) {
const warnedAboutRefsInRender = owner.stateNode._warnedAboutRefsInRender;
@@ -381,7 +393,19 @@ export function unstable_renderSubtreeIntoContainer(
);
}
let didWarnAboutUnmountComponentAtNode = false;
export function unmountComponentAtNode(container: Container) {
if (__DEV__) {
if (!didWarnAboutUnmountComponentAtNode) {
didWarnAboutUnmountComponentAtNode = true;
console.error(
'unmountComponentAtNode is deprecated and will be removed in the ' +
'next major release. Switch to the createRoot API. Learn ' +
'more: https://reactjs.org/link/switch-to-createroot',
);
}
}
if (!isValidContainerLegacy(container)) {
throw new Error(
'unmountComponentAtNode(...): Target container is not a DOM element.',

View File

@@ -100,6 +100,13 @@ function renderToStaticNodeStream(
children: ReactNodeList,
options?: ServerOptions,
): Readable {
if (__DEV__) {
console.error(
'ReactDOMServer.renderToStaticNodeStream() is deprecated.' +
' Use ReactDOMServer.renderToPipeableStream() and wait to `pipe` until the `onAllReady`' +
' callback has been called instead.',
);
}
return renderToNodeStreamImpl(children, options, true);
}

View File

@@ -34,7 +34,7 @@ const getFiberCurrentPropsFromNode = EventInternals[2];
const enqueueStateRestore = EventInternals[3];
const restoreStateIfNeeded = EventInternals[4];
const act = React.unstable_act;
const reactAct = React.unstable_act;
function Event(suffix) {}
@@ -121,7 +121,23 @@ function validateClassInstance(inst, methodName) {
* utilities will suffice for testing purposes.
* @lends ReactTestUtils
*/
let didWarnAboutReactTestUtilsDeprecation = false;
function renderIntoDocument(element) {
if (__DEV__) {
if (!didWarnAboutReactTestUtilsDeprecation) {
didWarnAboutReactTestUtilsDeprecation = true;
console.error(
'ReactDOMTestUtils is deprecated and will be removed in a future ' +
'major release, because it exposes internal implementation details ' +
'that are highly likely to change between releases. Upgrade to a ' +
'modern testing library, such as @testing-library/react. See ' +
'https://react.dev/warnings/react-dom-test-utils for more info.',
);
}
}
const div = document.createElement('div');
// None of our tests actually require attaching the container to the
// DOM, and doing so creates a mess that we rely on test isolation to
@@ -711,6 +727,23 @@ function buildSimulators() {
}
buildSimulators();
let didWarnAboutUsingAct = false;
export const act = __DEV__
? function actWithWarning(callback) {
if (__DEV__) {
if (!didWarnAboutUsingAct) {
didWarnAboutUsingAct = true;
console.error(
'`ReactDOMTestUtils.act` is deprecated in favor of `React.act`. ' +
'Import `act` from `react` instead of `react-dom/test-utils`. ' +
'See https://react.dev/warnings/react-dom-test-utils for more info.',
);
}
}
return reactAct(callback);
}
: reactAct;
export {
renderIntoDocument,
isElement,
@@ -729,5 +762,4 @@ export {
mockComponent,
nativeTouchData,
Simulate,
act,
};

View File

@@ -1,6 +1,6 @@
{
"name": "react-is",
"version": "18.1.0",
"version": "18.3.0",
"description": "Brand checking of React Elements.",
"main": "index.js",
"repository": {

View File

@@ -1385,25 +1385,36 @@ describe('ResponderEventPlugin', () => {
const ReactDOMComponentTree = require('react-dom/src/client/ReactDOMComponentTree');
class ChildComponent extends React.Component {
divRef = React.createRef();
div1Ref = React.createRef();
div2Ref = React.createRef();
render() {
return (
<div ref="DIV" id={this.props.id + '__DIV'}>
<div ref="DIV_1" id={this.props.id + '__DIV_1'} />
<div ref="DIV_2" id={this.props.id + '__DIV_2'} />
<div ref={this.divRef} id={this.props.id + '__DIV'}>
<div ref={this.div1Ref} id={this.props.id + '__DIV_1'} />
<div ref={this.div2Ref} id={this.props.id + '__DIV_2'} />
</div>
);
}
}
class ParentComponent extends React.Component {
pRef = React.createRef();
p_P1Ref = React.createRef();
p_P1Ref = React.createRef();
p_P1_C1Ref = React.createRef();
p_P1_C2Ref = React.createRef();
p_OneOffRef = React.createRef();
render() {
return (
<div ref="P" id="P">
<div ref="P_P1" id="P_P1">
<ChildComponent ref="P_P1_C1" id="P_P1_C1" />
<ChildComponent ref="P_P1_C2" id="P_P1_C2" />
<div ref={this.pRef} id="P">
<div ref={this.p_P1Ref} id="P_P1">
<ChildComponent ref={this.p_P1_C1Ref} id="P_P1_C1" />
<ChildComponent ref={this.p_P1_C2Ref} id="P_P1_C2" />
</div>
<div ref="P_OneOff" id="P_OneOff" />
<div ref={this.p_OneOffRef} id="P_OneOff" />
</div>
);
}
@@ -1414,41 +1425,45 @@ describe('ResponderEventPlugin', () => {
const ancestors = [
// Common ancestor with self is self.
{
one: parent.refs.P_P1_C1.refs.DIV_1,
two: parent.refs.P_P1_C1.refs.DIV_1,
com: parent.refs.P_P1_C1.refs.DIV_1,
one: parent.p_P1_C1Ref.current.div1Ref.current,
two: parent.p_P1_C1Ref.current.div1Ref.current,
com: parent.p_P1_C1Ref.current.div1Ref.current,
},
// Common ancestor with self is self - even if topmost DOM.
{one: parent.refs.P, two: parent.refs.P, com: parent.refs.P},
{
one: parent.pRef.current,
two: parent.pRef.current,
com: parent.pRef.current,
},
// Siblings
{
one: parent.refs.P_P1_C1.refs.DIV_1,
two: parent.refs.P_P1_C1.refs.DIV_2,
com: parent.refs.P_P1_C1.refs.DIV,
one: parent.p_P1_C1Ref.current.div1Ref.current,
two: parent.p_P1_C1Ref.current.div2Ref.current,
com: parent.p_P1_C1Ref.current.divRef.current,
},
// Common ancestor with parent is the parent.
{
one: parent.refs.P_P1_C1.refs.DIV_1,
two: parent.refs.P_P1_C1.refs.DIV,
com: parent.refs.P_P1_C1.refs.DIV,
one: parent.p_P1_C1Ref.current.div1Ref.current,
two: parent.p_P1_C1Ref.current.divRef.current,
com: parent.p_P1_C1Ref.current.divRef.current,
},
// Common ancestor with grandparent is the grandparent.
{
one: parent.refs.P_P1_C1.refs.DIV_1,
two: parent.refs.P_P1,
com: parent.refs.P_P1,
one: parent.p_P1_C1Ref.current.div1Ref.current,
two: parent.p_P1Ref.current,
com: parent.p_P1Ref.current,
},
// Grandparent across subcomponent boundaries.
{
one: parent.refs.P_P1_C1.refs.DIV_1,
two: parent.refs.P_P1_C2.refs.DIV_1,
com: parent.refs.P_P1,
one: parent.p_P1_C1Ref.current.div1Ref.current,
two: parent.p_P1_C2Ref.current.div1Ref.current,
com: parent.p_P1Ref.current,
},
// Something deep with something one-off.
{
one: parent.refs.P_P1_C1.refs.DIV_1,
two: parent.refs.P_OneOff,
com: parent.refs.P,
one: parent.p_P1_C1Ref.current.div1Ref.current,
two: parent.p_OneOffRef.current,
com: parent.pRef.current,
},
];
let i;

View File

@@ -26,7 +26,7 @@
"node": ">=0.10.0"
},
"peerDependencies": {
"react": "^18.1.0"
"react": "^18.3.0"
},
"dependencies": {
"loose-envify": "^1.1.0",

View File

@@ -34,7 +34,6 @@ import {
createFiberFromText,
createFiberFromPortal,
} from './ReactFiber.new';
import {emptyRefsObject} from './ReactFiberClassComponent.new';
import {isCompatibleFamilyForHotReloading} from './ReactFiberHotReloading.new';
import {StrictLegacyMode} from './ReactTypeOfMode';
import {getIsHydrating} from './ReactFiberHydrationContext.new';
@@ -92,6 +91,10 @@ if (__DEV__) {
};
}
function isReactClass(type) {
return type.prototype && type.prototype.isReactComponent;
}
function coerceRef(
returnFiber: Fiber,
current: Fiber | null,
@@ -115,7 +118,16 @@ function coerceRef(
element._owner &&
element._self &&
element._owner.stateNode !== element._self
)
) &&
// Will already throw with "Function components cannot have string refs"
!(
element._owner &&
((element._owner: any): Fiber).tag !== ClassComponent
) &&
// Will already warn with "Function components cannot be given refs"
!(typeof element.type === 'function' && !isReactClass(element.type)) &&
// Will already throw with "Element ref was specified as a string (someStringRef) but no owner was set"
element._owner
) {
const componentName =
getComponentNameFromFiber(returnFiber) || 'Component';
@@ -186,11 +198,7 @@ function coerceRef(
return current.ref;
}
const ref = function(value) {
let refs = resolvedInst.refs;
if (refs === emptyRefsObject) {
// This is a lazy pooled frozen object, so we need to initialize.
refs = resolvedInst.refs = {};
}
const refs = resolvedInst.refs;
if (value === null) {
delete refs[stringRef];
} else {

View File

@@ -34,7 +34,6 @@ import {
createFiberFromText,
createFiberFromPortal,
} from './ReactFiber.old';
import {emptyRefsObject} from './ReactFiberClassComponent.old';
import {isCompatibleFamilyForHotReloading} from './ReactFiberHotReloading.old';
import {StrictLegacyMode} from './ReactTypeOfMode';
import {getIsHydrating} from './ReactFiberHydrationContext.old';
@@ -92,6 +91,10 @@ if (__DEV__) {
};
}
function isReactClass(type) {
return type.prototype && type.prototype.isReactComponent;
}
function coerceRef(
returnFiber: Fiber,
current: Fiber | null,
@@ -115,7 +118,16 @@ function coerceRef(
element._owner &&
element._self &&
element._owner.stateNode !== element._self
)
) &&
// Will already throw with "Function components cannot have string refs"
!(
element._owner &&
((element._owner: any): Fiber).tag !== ClassComponent
) &&
// Will already warn with "Function components cannot be given refs"
!(typeof element.type === 'function' && !isReactClass(element.type)) &&
// Will already throw with "Element ref was specified as a string (someStringRef) but no owner was set"
element._owner
) {
const componentName =
getComponentNameFromFiber(returnFiber) || 'Component';
@@ -186,11 +198,7 @@ function coerceRef(
return current.ref;
}
const ref = function(value) {
let refs = resolvedInst.refs;
if (refs === emptyRefsObject) {
// This is a lazy pooled frozen object, so we need to initialize.
refs = resolvedInst.refs = {};
}
const refs = resolvedInst.refs;
if (value === null) {
delete refs[stringRef];
} else {

View File

@@ -496,6 +496,20 @@ function updateMemoComponent(
getComponentNameFromType(type),
);
}
if (
warnAboutDefaultPropsOnFunctionComponents &&
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,

View File

@@ -496,6 +496,20 @@ function updateMemoComponent(
getComponentNameFromType(type),
);
}
if (
warnAboutDefaultPropsOnFunctionComponents &&
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,

View File

@@ -12,7 +12,6 @@ import type {Lanes} from './ReactFiberLane.new';
import type {UpdateQueue} from './ReactFiberClassUpdateQueue.new';
import type {Flags} from './ReactFiberFlags';
import * as React from 'react';
import {
LayoutStatic,
MountLayoutDev,
@@ -82,10 +81,6 @@ import {
const fakeInternalInstance = {};
// React.Component uses a shared frozen object by default.
// We'll use it to determine whether we need to initialize legacy refs.
export const emptyRefsObject = new React.Component().refs;
let didWarnAboutStateAssignmentForComponent;
let didWarnAboutUninitializedState;
let didWarnAboutGetSnapshotBeforeUpdateWithoutDidUpdate;
@@ -96,6 +91,7 @@ let warnOnInvalidCallback;
let didWarnAboutDirectlyAssigningPropsToState;
let didWarnAboutContextTypeAndContextTypes;
let didWarnAboutInvalidateContextType;
let didWarnAboutLegacyContext;
if (__DEV__) {
didWarnAboutStateAssignmentForComponent = new Set();
@@ -106,6 +102,7 @@ if (__DEV__) {
didWarnAboutUndefinedDerivedState = new Set();
didWarnAboutContextTypeAndContextTypes = new Set();
didWarnAboutInvalidateContextType = new Set();
didWarnAboutLegacyContext = new Set();
const didWarnOnInvalidCallback = new Set();
@@ -435,6 +432,39 @@ function checkClassInstance(workInProgress: Fiber, ctor: any, newProps: any) {
);
}
} else {
if (
ctor.childContextTypes &&
!didWarnAboutLegacyContext.has(ctor) &&
// Strict Mode has its own warning for legacy context, so we can skip
// this one.
(workInProgress.mode & StrictLegacyMode) === NoMode
) {
didWarnAboutLegacyContext.add(ctor);
console.error(
'%s uses the legacy childContextTypes API which is no longer ' +
'supported and will be removed in the next major release. Use ' +
'React.createContext() instead\n\n.' +
'Learn more about this warning here: https://reactjs.org/link/legacy-context',
name,
);
}
if (
ctor.contextTypes &&
!didWarnAboutLegacyContext.has(ctor) &&
// Strict Mode has its own warning for legacy context, so we can skip
// this one.
(workInProgress.mode & StrictLegacyMode) === NoMode
) {
didWarnAboutLegacyContext.add(ctor);
console.error(
'%s uses the legacy contextTypes API which is no longer supported ' +
'and will be removed in the next major release. Use ' +
'React.createContext() with static contextType instead.\n\n' +
'Learn more about this warning here: https://reactjs.org/link/legacy-context',
name,
);
}
if (instance.contextTypes) {
console.error(
'contextTypes was defined as an instance property on %s. Use a static ' +
@@ -836,7 +866,7 @@ function mountClassInstance(
const instance = workInProgress.stateNode;
instance.props = newProps;
instance.state = workInProgress.memoizedState;
instance.refs = emptyRefsObject;
instance.refs = {};
initializeUpdateQueue(workInProgress);

View File

@@ -12,7 +12,6 @@ import type {Lanes} from './ReactFiberLane.old';
import type {UpdateQueue} from './ReactFiberClassUpdateQueue.old';
import type {Flags} from './ReactFiberFlags';
import * as React from 'react';
import {
LayoutStatic,
MountLayoutDev,
@@ -82,10 +81,6 @@ import {
const fakeInternalInstance = {};
// React.Component uses a shared frozen object by default.
// We'll use it to determine whether we need to initialize legacy refs.
export const emptyRefsObject = new React.Component().refs;
let didWarnAboutStateAssignmentForComponent;
let didWarnAboutUninitializedState;
let didWarnAboutGetSnapshotBeforeUpdateWithoutDidUpdate;
@@ -96,6 +91,7 @@ let warnOnInvalidCallback;
let didWarnAboutDirectlyAssigningPropsToState;
let didWarnAboutContextTypeAndContextTypes;
let didWarnAboutInvalidateContextType;
let didWarnAboutLegacyContext;
if (__DEV__) {
didWarnAboutStateAssignmentForComponent = new Set();
@@ -106,6 +102,7 @@ if (__DEV__) {
didWarnAboutUndefinedDerivedState = new Set();
didWarnAboutContextTypeAndContextTypes = new Set();
didWarnAboutInvalidateContextType = new Set();
didWarnAboutLegacyContext = new Set();
const didWarnOnInvalidCallback = new Set();
@@ -435,6 +432,39 @@ function checkClassInstance(workInProgress: Fiber, ctor: any, newProps: any) {
);
}
} else {
if (
ctor.childContextTypes &&
!didWarnAboutLegacyContext.has(ctor) &&
// Strict Mode has its own warning for legacy context, so we can skip
// this one.
(workInProgress.mode & StrictLegacyMode) === NoMode
) {
didWarnAboutLegacyContext.add(ctor);
console.error(
'%s uses the legacy childContextTypes API which is no longer ' +
'supported and will be removed in the next major release. Use ' +
'React.createContext() instead\n\n.' +
'Learn more about this warning here: https://reactjs.org/link/legacy-context',
name,
);
}
if (
ctor.contextTypes &&
!didWarnAboutLegacyContext.has(ctor) &&
// Strict Mode has its own warning for legacy context, so we can skip
// this one.
(workInProgress.mode & StrictLegacyMode) === NoMode
) {
didWarnAboutLegacyContext.add(ctor);
console.error(
'%s uses the legacy contextTypes API which is no longer supported ' +
'and will be removed in the next major release. Use ' +
'React.createContext() with static contextType instead.\n\n' +
'Learn more about this warning here: https://reactjs.org/link/legacy-context',
name,
);
}
if (instance.contextTypes) {
console.error(
'contextTypes was defined as an instance property on %s. Use a static ' +
@@ -836,7 +866,7 @@ function mountClassInstance(
const instance = workInProgress.stateNode;
instance.props = newProps;
instance.state = workInProgress.memoizedState;
instance.refs = emptyRefsObject;
instance.refs = {};
initializeUpdateQueue(workInProgress);

View File

@@ -0,0 +1,74 @@
/**
* 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 act;
describe('ReactFiberRefs', () => {
beforeEach(() => {
jest.resetModules();
React = require('react');
ReactNoop = require('react-noop-renderer');
act = require('jest-react').act;
});
test('strings refs can be codemodded to callback refs', async () => {
let app;
class App extends React.Component {
render() {
app = this;
return (
<div
prop="Hello!"
ref={el => {
// `refs` used to be a shared frozen object unless/until a string
// ref attached by the reconciler, but it's not anymore so that we
// can codemod string refs to userspace callback refs.
this.refs.div = el;
}}
/>
);
}
}
const root = ReactNoop.createRoot();
await act(async () => root.render(<App />));
expect(app.refs.div.prop).toBe('Hello!');
});
test('class refs are initialized to a frozen shared object', async () => {
const refsCollection = new Set();
class Component extends React.Component {
constructor(props) {
super(props);
refsCollection.add(this.refs);
}
render() {
return <div />;
}
}
const root = ReactNoop.createRoot();
await act(() =>
root.render(
<>
<Component />
<Component />
</>,
),
);
expect(refsCollection.size).toBe(1);
const refsInstance = Array.from(refsCollection)[0];
expect(Object.isFrozen(refsInstance)).toBe(__DEV__);
});
});

View File

@@ -11,6 +11,7 @@
'use strict';
let React;
let ReactFeatureFlags;
let ReactNoop;
let Scheduler;
@@ -19,6 +20,7 @@ describe('ReactIncrementalSideEffects', () => {
jest.resetModules();
React = require('react');
ReactFeatureFlags = require('shared/ReactFeatureFlags');
ReactNoop = require('react-noop-renderer');
Scheduler = require('scheduler');
});
@@ -1306,8 +1308,19 @@ describe('ReactIncrementalSideEffects', () => {
}
ReactNoop.render(<Foo />);
expect(Scheduler).toFlushWithoutYielding();
expect(() => {
expect(Scheduler).toFlushWithoutYielding();
}).toErrorDev(
ReactFeatureFlags.warnAboutStringRefs
? [
'Warning: Component "Foo" contains the string ref "bar". ' +
'Support for string refs will be removed in a future major release. ' +
'We recommend using useRef() or createRef() instead. ' +
'Learn more about using refs safely here: https://reactjs.org/link/strict-mode-string-ref\n' +
' in Foo (at **)',
]
: [],
);
expect(fooInstance.refs.bar.test).toEqual('test');
});
});

View File

@@ -293,7 +293,12 @@ describe('ReactLazy', () => {
await Promise.resolve();
expect(Scheduler).toFlushAndYield(['Hi']);
expect(() => expect(Scheduler).toFlushAndYield(['Hi'])).toErrorDev(
'Warning: T: Support for defaultProps ' +
'will be removed from function components in a future major ' +
'release. Use JavaScript default parameters instead.',
);
expect(root).toMatchRenderedOutput('Hi');
T.defaultProps = {text: 'Hi again'};
@@ -343,7 +348,14 @@ describe('ReactLazy', () => {
await Promise.resolve();
expect(Scheduler).toFlushAndYield(['Lazy', 'Sibling', 'A']);
expect(() =>
expect(Scheduler).toFlushAndYield(['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.',
);
expect(root).toMatchRenderedOutput('SiblingA');
// Lazy should not re-render
@@ -643,7 +655,12 @@ describe('ReactLazy', () => {
expect(root).not.toMatchRenderedOutput('Hi Bye');
await Promise.resolve();
expect(Scheduler).toFlushAndYield(['Hi Bye']);
expect(() => expect(Scheduler).toFlushAndYield(['Hi Bye'])).toErrorDev(
'Warning: T: Support for defaultProps ' +
'will be removed from function components in a future major ' +
'release. Use JavaScript default parameters instead.',
);
expect(root).toMatchRenderedOutput('Hi Bye');
root.update(
@@ -732,7 +749,11 @@ describe('ReactLazy', () => {
);
});
async function verifyInnerPropTypesAreChecked(Add) {
async function verifyInnerPropTypesAreChecked(
Add,
shouldWarnAboutFunctionDefaultProps,
shouldWarnAboutMemoDefaultProps,
) {
const LazyAdd = lazy(() => fakeImport(Add));
expect(() => {
LazyAdd.propTypes = {};
@@ -753,15 +774,28 @@ describe('ReactLazy', () => {
);
expect(Scheduler).toFlushAndYield(['Loading...']);
expect(root).not.toMatchRenderedOutput('22');
// Mount
await Promise.resolve();
expect(() => {
Scheduler.unstable_flushAll();
}).toErrorDev([
'Invalid prop `inner` of type `string` supplied to `Add`, expected `number`.',
]);
}).toErrorDev(
shouldWarnAboutFunctionDefaultProps
? [
'Add: Support for defaultProps will be removed from function components in a future major release. Use JavaScript default parameters instead.',
'Invalid prop `inner` of type `string` supplied to `Add`, expected `number`.',
]
: shouldWarnAboutMemoDefaultProps
? [
'Add: Support for defaultProps will be removed from memo components in a future major release. Use JavaScript default parameters instead.',
'Invalid prop `inner` of type `string` supplied to `Add`, expected `number`.',
]
: [
'Invalid prop `inner` of type `string` supplied to `Add`, expected `number`.',
],
);
expect(root).toMatchRenderedOutput('22');
// Update
@@ -792,7 +826,7 @@ describe('ReactLazy', () => {
Add.defaultProps = {
innerWithDefault: 42,
};
await verifyInnerPropTypesAreChecked(Add);
await verifyInnerPropTypesAreChecked(Add, true);
});
it('respects propTypes on function component without defaultProps', async () => {
@@ -874,7 +908,7 @@ describe('ReactLazy', () => {
Add.defaultProps = {
innerWithDefault: 42,
};
await verifyInnerPropTypesAreChecked(Add);
await verifyInnerPropTypesAreChecked(Add, false, true);
});
it('respects propTypes on outer memo component without defaultProps', async () => {
@@ -901,7 +935,7 @@ describe('ReactLazy', () => {
Add.defaultProps = {
innerWithDefault: 42,
};
await verifyInnerPropTypesAreChecked(React.memo(Add));
await verifyInnerPropTypesAreChecked(React.memo(Add), true);
});
it('respects propTypes on inner memo component without defaultProps', async () => {
@@ -944,9 +978,10 @@ describe('ReactLazy', () => {
await Promise.resolve();
expect(() => {
expect(Scheduler).toFlushAndYield(['Inner default text']);
}).toErrorDev(
}).toErrorDev([
'T: Support for defaultProps will be removed from function components in a future major release. Use JavaScript default parameters instead.',
'The prop `text` is marked as required in `T`, but its value is `undefined`',
);
]);
expect(root).toMatchRenderedOutput('Inner default text');
// Update
@@ -1058,7 +1093,11 @@ describe('ReactLazy', () => {
// Mount
await Promise.resolve();
expect(Scheduler).toFlushWithoutYielding();
expect(() => {
expect(Scheduler).toFlushWithoutYielding();
}).toErrorDev(
'Unknown: Support for defaultProps will be removed from memo components in a future major release. Use JavaScript default parameters instead.',
);
expect(root).toMatchRenderedOutput('4');
// Update (shallowly equal)
@@ -1142,7 +1181,12 @@ describe('ReactLazy', () => {
// Mount
await Promise.resolve();
expect(Scheduler).toFlushWithoutYielding();
expect(() => {
expect(Scheduler).toFlushWithoutYielding();
}).toErrorDev([
'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.',
]);
expect(root).toMatchRenderedOutput('4');
// Update

View File

@@ -75,6 +75,7 @@ describe('memo', () => {
}
ReactNoop.render(<Outer />);
expect(() => expect(Scheduler).toFlushWithoutYielding()).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.',
]);
@@ -441,7 +442,11 @@ describe('memo', () => {
);
expect(Scheduler).toFlushAndYield(['Loading...']);
await Promise.resolve();
expect(Scheduler).toFlushAndYield([15]);
expect(() => {
expect(Scheduler).toFlushAndYield([15]);
}).toErrorDev([
'Counter: Support for defaultProps will be removed from memo components in a future major release. Use JavaScript default parameters instead.',
]);
expect(ReactNoop.getChildren()).toEqual([span(15)]);
// Should bail out because props have not changed
@@ -552,7 +557,11 @@ describe('memo', () => {
<Outer />
</div>,
);
expect(Scheduler).toFlushWithoutYielding();
expect(() => {
expect(Scheduler).toFlushWithoutYielding();
}).toErrorDev([
'Inner: Support for defaultProps will be removed from memo components in a future major release. Use JavaScript default parameters instead.',
]);
// Mount
expect(() => {

View File

@@ -1,6 +1,6 @@
{
"name": "react-test-renderer",
"version": "18.1.0",
"version": "18.3.0",
"description": "React package for snapshot testing.",
"main": "index.js",
"repository": {
@@ -19,12 +19,12 @@
},
"homepage": "https://reactjs.org/",
"dependencies": {
"react-is": "^18.1.0",
"react-is": "^18.3.0",
"react-shallow-renderer": "^16.15.0",
"scheduler": "^0.22.0"
},
"peerDependencies": {
"react": "^18.1.0"
"react": "^18.3.0"
},
"files": [
"LICENSE",

View File

@@ -271,13 +271,15 @@ describe('ReactTestRenderer', () => {
return <div>Hello, world</div>;
}
class Foo extends React.Component {
fooRef = React.createRef();
render() {
return <Bar ref="foo" />;
return <Bar ref={this.fooRef} />;
}
}
class Baz extends React.Component {
bazRef = React.createRef();
render() {
return <div ref="baz" />;
return <div ref={this.bazRef} />;
}
}
ReactTestRenderer.create(<Baz />);
@@ -298,11 +300,12 @@ describe('ReactTestRenderer', () => {
const mockAnchorInstance = {hover: () => {}};
const log = [];
class Foo extends React.Component {
barRef = React.createRef();
componentDidMount() {
log.push(this.refs.bar);
log.push(this.barRef.current);
}
render() {
return <a ref="bar">Hello, world</a>;
return <a ref={this.barRef}>Hello, world</a>;
}
}
function createNodeMock(element) {
@@ -355,7 +358,7 @@ describe('ReactTestRenderer', () => {
it('supports unmounting when using refs', () => {
class Foo extends React.Component {
render() {
return <div ref="foo" />;
return <div ref={React.createRef()} />;
}
}
const inst = ReactTestRenderer.create(<Foo />, {
@@ -394,7 +397,11 @@ describe('ReactTestRenderer', () => {
};
class Foo extends React.Component {
render() {
return this.props.useDiv ? <div ref="foo" /> : <span ref="foo" />;
return this.props.useDiv ? (
<div ref={React.createRef()} />
) : (
<span ref={React.createRef()} />
);
}
}
const inst = ReactTestRenderer.create(<Foo useDiv={true} />, {

View File

@@ -4,7 +4,7 @@
"keywords": [
"react"
],
"version": "18.1.0",
"version": "18.3.0",
"homepage": "https://reactjs.org/",
"bugs": "https://github.com/facebook/react/issues",
"license": "MIT",

View File

@@ -279,6 +279,8 @@ function validateFragmentProps(fragment) {
}
}
const didWarnAboutKeySpread = {};
export function jsxWithValidation(
type,
props,
@@ -287,115 +289,132 @@ export function jsxWithValidation(
source,
self,
) {
const validType = isValidElementType(type);
// We warn in this case but don't throw. We expect the element creation to
// succeed and there will likely be errors in render.
if (!validType) {
let info = '';
if (
type === undefined ||
(typeof type === 'object' &&
type !== null &&
Object.keys(type).length === 0)
) {
info +=
' You likely forgot to export your component from the file ' +
"it's defined in, or you might have mixed up default and named imports.";
}
const sourceInfo = getSourceInfoErrorAddendum(source);
if (sourceInfo) {
info += sourceInfo;
} else {
info += getDeclarationErrorAddendum();
}
let typeString;
if (type === null) {
typeString = 'null';
} else if (isArray(type)) {
typeString = 'array';
} else if (type !== undefined && type.$$typeof === REACT_ELEMENT_TYPE) {
typeString = `<${getComponentNameFromType(type.type) || 'Unknown'} />`;
info =
' Did you accidentally export a JSX literal instead of a component?';
} else {
typeString = typeof type;
}
if (__DEV__) {
console.error(
'React.jsx: type is invalid -- expected a string (for ' +
'built-in components) or a class/function (for composite ' +
'components) but got: %s.%s',
typeString,
info,
);
}
}
const element = jsxDEV(type, props, key, source, self);
// The result can be nullish if a mock or a custom function is used.
// TODO: Drop this when these are no longer allowed as the type argument.
if (element == null) {
return element;
}
// Skip key warning if the type isn't valid since our key validation logic
// doesn't expect a non-string/function type and can throw confusing errors.
// We don't want exception behavior to differ between dev and prod.
// (Rendering will throw with a helpful message and as soon as the type is
// fixed, the key warnings will appear.)
if (validType) {
const children = props.children;
if (children !== undefined) {
if (isStaticChildren) {
if (isArray(children)) {
for (let i = 0; i < children.length; i++) {
validateChildKeys(children[i], type);
}
if (Object.freeze) {
Object.freeze(children);
}
} else {
if (__DEV__) {
console.error(
'React.jsx: Static children should always be an array. ' +
'You are likely explicitly calling React.jsxs or React.jsxDEV. ' +
'Use the Babel transform instead.',
);
}
}
} else {
validateChildKeys(children, type);
}
}
}
if (__DEV__) {
if (warnAboutSpreadingKeyToJSX) {
if (hasOwnProperty.call(props, 'key')) {
const validType = isValidElementType(type);
// We warn in this case but don't throw. We expect the element creation to
// succeed and there will likely be errors in render.
if (!validType) {
let info = '';
if (
type === undefined ||
(typeof type === 'object' &&
type !== null &&
Object.keys(type).length === 0)
) {
info +=
' You likely forgot to export your component from the file ' +
"it's defined in, or you might have mixed up default and named imports.";
}
const sourceInfo = getSourceInfoErrorAddendum(source);
if (sourceInfo) {
info += sourceInfo;
} else {
info += getDeclarationErrorAddendum();
}
let typeString;
if (type === null) {
typeString = 'null';
} else if (isArray(type)) {
typeString = 'array';
} else if (type !== undefined && type.$$typeof === REACT_ELEMENT_TYPE) {
typeString = `<${getComponentNameFromType(type.type) || 'Unknown'} />`;
info =
' Did you accidentally export a JSX literal instead of a component?';
} else {
typeString = typeof type;
}
if (__DEV__) {
console.error(
'React.jsx: Spreading a key to JSX is a deprecated pattern. ' +
'Explicitly pass a key after spreading props in your JSX call. ' +
'E.g. <%s {...props} key={key} />',
getComponentNameFromType(type) || 'ComponentName',
'React.jsx: type is invalid -- expected a string (for ' +
'built-in components) or a class/function (for composite ' +
'components) but got: %s.%s',
typeString,
info,
);
}
}
}
if (type === REACT_FRAGMENT_TYPE) {
validateFragmentProps(element);
} else {
validatePropTypes(element);
}
const element = jsxDEV(type, props, key, source, self);
return element;
// The result can be nullish if a mock or a custom function is used.
// TODO: Drop this when these are no longer allowed as the type argument.
if (element == null) {
return element;
}
// Skip key warning if the type isn't valid since our key validation logic
// doesn't expect a non-string/function type and can throw confusing errors.
// We don't want exception behavior to differ between dev and prod.
// (Rendering will throw with a helpful message and as soon as the type is
// fixed, the key warnings will appear.)
if (validType) {
const children = props.children;
if (children !== undefined) {
if (isStaticChildren) {
if (isArray(children)) {
for (let i = 0; i < children.length; i++) {
validateChildKeys(children[i], type);
}
if (Object.freeze) {
Object.freeze(children);
}
} else {
if (__DEV__) {
console.error(
'React.jsx: Static children should always be an array. ' +
'You are likely explicitly calling React.jsxs or React.jsxDEV. ' +
'Use the Babel transform instead.',
);
}
}
} else {
validateChildKeys(children, type);
}
}
}
if (warnAboutSpreadingKeyToJSX) {
if (hasOwnProperty.call(props, 'key')) {
const componentName = getComponentNameFromType(type);
const keys = Object.keys(props).filter(k => k !== 'key');
const beforeExample =
keys.length > 0
? '{key: someKey, ' + keys.join(': ..., ') + ': ...}'
: '{key: someKey}';
if (!didWarnAboutKeySpread[componentName + beforeExample]) {
const afterExample =
keys.length > 0 ? '{' + keys.join(': ..., ') + ': ...}' : '{}';
console.error(
'A props object containing a "key" prop is being spread into JSX:\n' +
' let props = %s;\n' +
' <%s {...props} />\n' +
'React keys must be passed directly to JSX without using spread:\n' +
' let props = %s;\n' +
' <%s key={someKey} {...props} />',
beforeExample,
componentName,
afterExample,
componentName,
);
didWarnAboutKeySpread[componentName + beforeExample] = true;
}
}
}
if (type === REACT_FRAGMENT_TYPE) {
validateFragmentProps(element);
} else {
validatePropTypes(element);
}
return element;
}
}
// These two functions exist to still get child warnings in dev

View File

@@ -9,6 +9,7 @@ PropTypes = null
React = null
ReactDOM = null
ReactDOMClient = null
ReactFeatureFlags = null
act = null
describe 'ReactCoffeeScriptClass', ->
@@ -22,6 +23,7 @@ describe 'ReactCoffeeScriptClass', ->
React = require 'react'
ReactDOM = require 'react-dom'
ReactDOMClient = require 'react-dom/client'
ReactFeatureFlags = require 'shared/ReactFeatureFlags'
act = require('jest-react').act
PropTypes = require 'prop-types'
container = document.createElement 'div'
@@ -528,7 +530,7 @@ describe 'ReactCoffeeScriptClass', ->
test React.createElement(Foo), 'DIV', 'bar-through-context'
it 'supports classic refs', ->
it 'supports string refs', ->
class Foo extends React.Component
render: ->
React.createElement(InnerComponent,
@@ -537,7 +539,19 @@ describe 'ReactCoffeeScriptClass', ->
)
ref = React.createRef()
test(React.createElement(Foo, ref: ref), 'DIV', 'foo')
expect(->
test(React.createElement(Foo, ref: ref), 'DIV', 'foo')
).toErrorDev(
if ReactFeatureFlags.warnAboutStringRefs
then [
'Warning: Component "Foo" contains the string ref "inner". ' +
'Support for string refs will be removed in a future major release. ' +
'We recommend using useRef() or createRef() instead. ' +
'Learn more about using refs safely here: https://reactjs.org/link/strict-mode-string-ref\n' +
' in Foo (at **)'
]
else []
);
expect(ref.current.refs.inner.getName()).toBe 'foo'
it 'supports drilling through to the DOM using findDOMNode', ->

View File

@@ -46,6 +46,8 @@ describe('ReactContextValidator', () => {
};
class ComponentInFooBarContext extends React.Component {
childRef = React.createRef();
getChildContext() {
return {
foo: 'abc',
@@ -54,7 +56,7 @@ describe('ReactContextValidator', () => {
}
render() {
return <Component ref="child" />;
return <Component ref={this.childRef} />;
}
}
ComponentInFooBarContext.childContextTypes = {
@@ -65,7 +67,7 @@ describe('ReactContextValidator', () => {
const instance = ReactTestUtils.renderIntoDocument(
<ComponentInFooBarContext />,
);
expect(instance.refs.child.context).toEqual({foo: 'abc'});
expect(instance.childRef.current.context).toEqual({foo: 'abc'});
});
it('should pass next context to lifecycles', () => {

View File

@@ -13,6 +13,7 @@ let PropTypes;
let React;
let ReactDOM;
let ReactDOMClient;
let ReactFeatureFlags;
let act;
describe('ReactES6Class', () => {
@@ -31,6 +32,7 @@ describe('ReactES6Class', () => {
React = require('react');
ReactDOM = require('react-dom');
ReactDOMClient = require('react-dom/client');
ReactFeatureFlags = require('shared/ReactFeatureFlags');
act = require('jest-react').act;
container = document.createElement('div');
root = ReactDOMClient.createRoot(container);
@@ -568,14 +570,26 @@ describe('ReactES6Class', () => {
test(<Foo />, 'DIV', 'bar-through-context');
});
it('supports classic refs', () => {
it('supports string refs', () => {
class Foo extends React.Component {
render() {
return <Inner name="foo" ref="inner" />;
}
}
const ref = React.createRef();
test(<Foo ref={ref} />, 'DIV', 'foo');
expect(() => {
test(<Foo ref={ref} />, 'DIV', 'foo');
}).toErrorDev(
ReactFeatureFlags.warnAboutStringRefs
? [
'Warning: Component "Foo" contains the string ref "inner". ' +
'Support for string refs will be removed in a future major release. ' +
'We recommend using useRef() or createRef() instead. ' +
'Learn more about using refs safely here: https://reactjs.org/link/strict-mode-string-ref\n' +
' in Foo (at **)',
]
: [],
);
expect(ref.current.refs.inner.getName()).toBe('foo');
});

View File

@@ -114,7 +114,7 @@ describe('ReactElement', () => {
render() {
return (
<div>
<Child ref="childElement" />
<Child ref={React.createRef()} />
</div>
);
}

View File

@@ -82,8 +82,10 @@ describe('ReactElementClone', () => {
it('should keep the original ref if it is not overridden', () => {
class Grandparent extends React.Component {
yoloRef = React.createRef();
render() {
return <Parent child={<div ref="yolo" />} />;
return <Parent child={<div ref={this.yoloRef} />} />;
}
}
@@ -96,7 +98,7 @@ describe('ReactElementClone', () => {
}
const component = ReactTestUtils.renderIntoDocument(<Grandparent />);
expect(component.refs.yolo.tagName).toBe('DIV');
expect(component.yoloRef.current.tagName).toBe('DIV');
});
it('should transfer the key property', () => {
@@ -174,21 +176,25 @@ describe('ReactElementClone', () => {
it('should support keys and refs', () => {
class Parent extends React.Component {
xyzRef = React.createRef();
render() {
const clone = React.cloneElement(this.props.children, {
key: 'xyz',
ref: 'xyz',
ref: this.xyzRef,
});
expect(clone.key).toBe('xyz');
expect(clone.ref).toBe('xyz');
expect(clone.ref).toBe(this.xyzRef);
return <div>{clone}</div>;
}
}
class Grandparent extends React.Component {
parentRef = React.createRef();
render() {
return (
<Parent ref="parent">
<Parent ref={this.parentRef}>
<span key="abc" />
</Parent>
);
@@ -196,30 +202,37 @@ describe('ReactElementClone', () => {
}
const component = ReactTestUtils.renderIntoDocument(<Grandparent />);
expect(component.refs.parent.refs.xyz.tagName).toBe('SPAN');
expect(component.parentRef.current.xyzRef.current.tagName).toBe('SPAN');
});
it('should steal the ref if a new ref is specified', () => {
class Parent extends React.Component {
xyzRef = React.createRef();
render() {
const clone = React.cloneElement(this.props.children, {ref: 'xyz'});
const clone = React.cloneElement(this.props.children, {
ref: this.xyzRef,
});
return <div>{clone}</div>;
}
}
class Grandparent extends React.Component {
parentRef = React.createRef();
childRef = React.createRef();
render() {
return (
<Parent ref="parent">
<span ref="child" />
<Parent ref={this.parentRef}>
<span ref={this.childRef} />
</Parent>
);
}
}
const component = ReactTestUtils.renderIntoDocument(<Grandparent />);
expect(component.refs.child).toBeUndefined();
expect(component.refs.parent.refs.xyz.tagName).toBe('SPAN');
expect(component.childRef).toEqual({current: null});
expect(component.parentRef.current.xyzRef.current.tagName).toBe('SPAN');
});
it('should overwrite props', () => {

View File

@@ -280,7 +280,7 @@ describe('ReactElement.jsx', () => {
class Parent extends React.Component {
render() {
return JSXRuntime.jsx('div', {
children: JSXRuntime.jsx(Child, {ref: 'childElement'}),
children: JSXRuntime.jsx(Child, {ref: React.createRef()}),
});
}
}
@@ -429,16 +429,19 @@ describe('ReactElement.jsx', () => {
class Parent extends React.Component {
render() {
return JSXRuntime.jsx('div', {
children: [JSXRuntime.jsx(Child, {key: '0'})],
children: [JSXRuntime.jsx(Child, {key: '0', prop: 'hi'})],
});
}
}
expect(() =>
ReactDOM.render(JSXRuntime.jsx(Parent, {}), container),
).toErrorDev(
'Warning: React.jsx: Spreading a key to JSX is a deprecated pattern. ' +
'Explicitly pass a key after spreading props in your JSX call. ' +
'E.g. <Child {...props} key={key} />',
'Warning: A props object containing a "key" prop is being spread into JSX:\n' +
' let props = {key: someKey, prop: ...};\n' +
' <Child {...props} />\n' +
'React keys must be passed directly to JSX without using spread:\n' +
' let props = {prop: ...};\n' +
' <Child key={someKey} {...props} />',
);
});
}

View File

@@ -78,10 +78,11 @@ describe('ReactJSXElement', () => {
});
it('extracts key and ref from the rest of the props', () => {
const element = <Component key="12" ref="34" foo="56" />;
const ref = React.createRef();
const element = <Component key="12" ref={ref} foo="56" />;
expect(element.type).toBe(Component);
expect(element.key).toBe('12');
expect(element.ref).toBe('34');
expect(element.ref).toBe(ref);
const expectation = {foo: '56'};
Object.freeze(expectation);
expect(element.props).toEqual(expectation);

View File

@@ -765,12 +765,18 @@ describe('string refs', () => {
expect(() => {
ReactDOM.render(<OuterComponent />, container);
}).toErrorDev(
'Warning: A string ref, "somestring", has been found within a strict mode tree. ' +
'String refs are a source of potential bugs and should be avoided. ' +
'We recommend using useRef() or createRef() instead. ' +
'Learn more about using refs safely here: ' +
'https://reactjs.org/link/strict-mode-string-ref\n' +
' in OuterComponent (at **)',
ReactFeatureFlags.warnAboutStringRefs
? 'Warning: Component "StrictMode" contains the string ref "somestring". ' +
'Support for string refs will be removed in a future major release. ' +
'We recommend using useRef() or createRef() instead. ' +
'Learn more about using refs safely here: https://reactjs.org/link/strict-mode-string-ref\n' +
' in OuterComponent (at **)'
: 'Warning: A string ref, "somestring", has been found within a strict mode tree. ' +
'String refs are a source of potential bugs and should be avoided. ' +
'We recommend using useRef() or createRef() instead. ' +
'Learn more about using refs safely here: ' +
'https://reactjs.org/link/strict-mode-string-ref\n' +
' in OuterComponent (at **)',
);
// Dedup
@@ -806,13 +812,20 @@ describe('string refs', () => {
expect(() => {
ReactDOM.render(<OuterComponent />, container);
}).toErrorDev(
'Warning: A string ref, "somestring", has been found within a strict mode tree. ' +
'String refs are a source of potential bugs and should be avoided. ' +
'We recommend using useRef() or createRef() instead. ' +
'Learn more about using refs safely here: ' +
'https://reactjs.org/link/strict-mode-string-ref\n' +
' in InnerComponent (at **)\n' +
' in OuterComponent (at **)',
ReactFeatureFlags.warnAboutStringRefs
? 'Warning: Component "InnerComponent" contains the string ref "somestring". ' +
'Support for string refs will be removed in a future major release. ' +
'We recommend using useRef() or createRef() instead. ' +
'Learn more about using refs safely here: https://reactjs.org/link/strict-mode-string-ref\n' +
' in InnerComponent (at **)\n' +
' in OuterComponent (at **)'
: 'Warning: A string ref, "somestring", has been found within a strict mode tree. ' +
'String refs are a source of potential bugs and should be avoided. ' +
'We recommend using useRef() or createRef() instead. ' +
'Learn more about using refs safely here: ' +
'https://reactjs.org/link/strict-mode-string-ref\n' +
' in InnerComponent (at **)\n' +
' in OuterComponent (at **)',
);
// Dedup

View File

@@ -17,6 +17,7 @@ import ReactDOMClient = require('react-dom/client');
import ReactDOMTestUtils = require('react-dom/test-utils');
import PropTypes = require('prop-types');
import internalAct = require('jest-react');
import ReactFeatureFlags = require('shared/ReactFeatureFlags')
// Before Each
@@ -686,9 +687,21 @@ describe('ReactTypeScriptClass', function() {
test(React.createElement(ProvideContext), 'DIV', 'bar-through-context');
});
it('supports classic refs', function() {
it('supports string refs', function() {
const ref = React.createRef();
test(React.createElement(ClassicRefs, {ref: ref}), 'DIV', 'foo');
expect(() => {
test(React.createElement(ClassicRefs, {ref: ref}), 'DIV', 'foo');
}).toErrorDev(
ReactFeatureFlags.warnAboutStringRefs
? [
'Warning: Component "ClassicRefs" contains the string ref "inner". ' +
'Support for string refs will be removed in a future major release. ' +
'We recommend using useRef() or createRef() instead. ' +
'Learn more about using refs safely here: https://reactjs.org/link/strict-mode-string-ref\n' +
' in ClassicRefs (at **)',
]
: [],
);
expect(ref.current.refs.inner.getName()).toBe('foo');
});

View File

@@ -294,6 +294,8 @@ function validateFragmentProps(fragment) {
}
}
const didWarnAboutKeySpread = {};
export function jsxWithValidation(
type,
props,
@@ -390,12 +392,29 @@ export function jsxWithValidation(
if (warnAboutSpreadingKeyToJSX) {
if (hasOwnProperty.call(props, 'key')) {
console.error(
'React.jsx: Spreading a key to JSX is a deprecated pattern. ' +
'Explicitly pass a key after spreading props in your JSX call. ' +
'E.g. <%s {...props} key={key} />',
getComponentNameFromType(type) || 'ComponentName',
);
const componentName = getComponentNameFromType(type);
const keys = Object.keys(props).filter(k => k !== 'key');
const beforeExample =
keys.length > 0
? '{key: someKey, ' + keys.join(': ..., ') + ': ...}'
: '{key: someKey}';
if (!didWarnAboutKeySpread[componentName + beforeExample]) {
const afterExample =
keys.length > 0 ? '{' + keys.join(': ..., ') + ': ...}' : '{}';
console.error(
'A props object containing a "key" prop is being spread into JSX:\n' +
' let props = %s;\n' +
' <%s {...props} />\n' +
'React keys must be passed directly to JSX without using spread:\n' +
' let props = %s;\n' +
' <%s key={someKey} {...props} />',
beforeExample,
componentName,
afterExample,
componentName,
);
didWarnAboutKeySpread[componentName + beforeExample] = true;
}
}
}

View File

@@ -205,13 +205,13 @@ export const disableTextareaChildren = false;
// Part of the simplification of React.createElement so we can eventually move
// from React.createElement to React.jsx
// https://github.com/reactjs/rfcs/blob/createlement-rfc/text/0000-create-element-changes.md
export const warnAboutDefaultPropsOnFunctionComponents = false; // deprecate later, not 18.0
export const warnAboutDefaultPropsOnFunctionComponents = true; // deprecate later, not 18.0
// Enables a warning when trying to spread a 'key' to an element;
// a deprecated pattern we want to get rid of in the future
export const warnAboutSpreadingKeyToJSX = false;
export const warnAboutSpreadingKeyToJSX = true;
export const warnAboutStringRefs = false;
export const warnAboutStringRefs = true;
// -----------------------------------------------------------------------------
// Debugging and DevTools

View File

@@ -13,4 +13,4 @@
// TODO: This module is used both by the release scripts and to expose a version
// at runtime. We should instead inject the version number as part of the build
// process, and use the ReactVersions.js module as the single source of truth.
export default '18.1.0';
export default '18.3.0';

View File

@@ -39,14 +39,14 @@ export const warnAboutDeprecatedLifecycles = true;
export const enableScopeAPI = false;
export const enableCreateEventHandleAPI = false;
export const enableSuspenseCallback = false;
export const warnAboutDefaultPropsOnFunctionComponents = false;
export const warnAboutStringRefs = false;
export const warnAboutDefaultPropsOnFunctionComponents = true;
export const warnAboutStringRefs = true;
export const disableLegacyContext = false;
export const disableSchedulerTimeoutBasedOnReactExpirationTime = false;
export const enableTrustedTypesIntegration = false;
export const disableTextareaChildren = false;
export const disableModulePatternComponents = false;
export const warnAboutSpreadingKeyToJSX = false;
export const warnAboutSpreadingKeyToJSX = true;
export const enableSuspenseAvoidThisFallback = false;
export const enableSuspenseAvoidThisFallbackFizz = false;
export const enableCPUSuspense = true;

View File

@@ -29,14 +29,14 @@ export const enableSchedulerDebugging = false;
export const enableScopeAPI = false;
export const enableCreateEventHandleAPI = false;
export const enableSuspenseCallback = false;
export const warnAboutDefaultPropsOnFunctionComponents = false;
export const warnAboutStringRefs = false;
export const warnAboutDefaultPropsOnFunctionComponents = true;
export const warnAboutStringRefs = true;
export const disableLegacyContext = false;
export const disableSchedulerTimeoutBasedOnReactExpirationTime = false;
export const enableTrustedTypesIntegration = false;
export const disableTextareaChildren = false;
export const disableModulePatternComponents = false;
export const warnAboutSpreadingKeyToJSX = false;
export const warnAboutSpreadingKeyToJSX = true;
export const enableSuspenseAvoidThisFallback = false;
export const enableSuspenseAvoidThisFallbackFizz = false;
export const enableCPUSuspense = false;

View File

@@ -29,14 +29,14 @@ export const enableSchedulerDebugging = false;
export const enableScopeAPI = false;
export const enableCreateEventHandleAPI = false;
export const enableSuspenseCallback = false;
export const warnAboutDefaultPropsOnFunctionComponents = false;
export const warnAboutStringRefs = false;
export const warnAboutDefaultPropsOnFunctionComponents = true;
export const warnAboutStringRefs = true;
export const disableLegacyContext = false;
export const disableSchedulerTimeoutBasedOnReactExpirationTime = false;
export const enableTrustedTypesIntegration = false;
export const disableTextareaChildren = false;
export const disableModulePatternComponents = false;
export const warnAboutSpreadingKeyToJSX = false;
export const warnAboutSpreadingKeyToJSX = true;
export const enableSuspenseAvoidThisFallback = false;
export const enableSuspenseAvoidThisFallbackFizz = false;
export const enableCPUSuspense = false;

View File

@@ -29,14 +29,14 @@ export const enableSchedulerDebugging = false;
export const enableScopeAPI = false;
export const enableCreateEventHandleAPI = false;
export const enableSuspenseCallback = false;
export const warnAboutDefaultPropsOnFunctionComponents = false;
export const warnAboutStringRefs = false;
export const warnAboutDefaultPropsOnFunctionComponents = true;
export const warnAboutStringRefs = true;
export const disableLegacyContext = false;
export const disableSchedulerTimeoutBasedOnReactExpirationTime = false;
export const enableTrustedTypesIntegration = false;
export const disableTextareaChildren = false;
export const disableModulePatternComponents = false;
export const warnAboutSpreadingKeyToJSX = false;
export const warnAboutSpreadingKeyToJSX = true;
export const enableComponentStackLocations = false;
export const enableLegacyFBSupport = false;
export const enableFilterEmptyStringAttributesDOM = false;

View File

@@ -29,14 +29,14 @@ export const disableInputAttributeSyncing = false;
export const enableScopeAPI = true;
export const enableCreateEventHandleAPI = false;
export const enableSuspenseCallback = true;
export const warnAboutDefaultPropsOnFunctionComponents = false;
export const warnAboutStringRefs = false;
export const warnAboutDefaultPropsOnFunctionComponents = true;
export const warnAboutStringRefs = true;
export const disableLegacyContext = false;
export const disableSchedulerTimeoutBasedOnReactExpirationTime = false;
export const enableTrustedTypesIntegration = false;
export const disableTextareaChildren = false;
export const disableModulePatternComponents = true;
export const warnAboutSpreadingKeyToJSX = false;
export const warnAboutSpreadingKeyToJSX = true;
export const enableSuspenseAvoidThisFallback = true;
export const enableSuspenseAvoidThisFallbackFizz = false;
export const enableCPUSuspense = false;

View File

@@ -29,14 +29,14 @@ export const enableSchedulerDebugging = false;
export const enableScopeAPI = false;
export const enableCreateEventHandleAPI = false;
export const enableSuspenseCallback = false;
export const warnAboutDefaultPropsOnFunctionComponents = false;
export const warnAboutStringRefs = false;
export const warnAboutDefaultPropsOnFunctionComponents = true;
export const warnAboutStringRefs = true;
export const disableLegacyContext = false;
export const disableSchedulerTimeoutBasedOnReactExpirationTime = false;
export const enableTrustedTypesIntegration = false;
export const disableTextareaChildren = false;
export const disableModulePatternComponents = false;
export const warnAboutSpreadingKeyToJSX = false;
export const warnAboutSpreadingKeyToJSX = true;
export const enableSuspenseAvoidThisFallback = false;
export const enableSuspenseAvoidThisFallbackFizz = false;
export const enableCPUSuspense = false;

View File

@@ -29,14 +29,14 @@ export const enableSchedulerDebugging = false;
export const enableScopeAPI = true;
export const enableCreateEventHandleAPI = true;
export const enableSuspenseCallback = true;
export const warnAboutDefaultPropsOnFunctionComponents = false;
export const warnAboutStringRefs = false;
export const warnAboutDefaultPropsOnFunctionComponents = true;
export const warnAboutStringRefs = true;
export const disableLegacyContext = __EXPERIMENTAL__;
export const disableSchedulerTimeoutBasedOnReactExpirationTime = false;
export const enableTrustedTypesIntegration = false;
export const disableTextareaChildren = __EXPERIMENTAL__;
export const disableModulePatternComponents = true;
export const warnAboutSpreadingKeyToJSX = false;
export const warnAboutSpreadingKeyToJSX = true;
export const enableSuspenseAvoidThisFallback = true;
export const enableSuspenseAvoidThisFallbackFizz = false;
export const enableCPUSuspense = true;

View File

@@ -63,8 +63,8 @@ export const enableSchedulingProfiler =
export const enableSchedulerDebugging = true;
export const warnAboutDeprecatedLifecycles = true;
export const disableLegacyContext = __EXPERIMENTAL__;
export const warnAboutStringRefs = false;
export const warnAboutDefaultPropsOnFunctionComponents = false;
export const warnAboutStringRefs = true;
export const warnAboutDefaultPropsOnFunctionComponents = true;
export const enableGetInspectorDataForInstanceInProduction = false;
export const enableCache = true;

View File

@@ -23,6 +23,24 @@ module.exports = function shouldIgnoreConsoleError(format, args) {
// We haven't finished migrating our tests to use createRoot.
return true;
}
if (
format.indexOf(
'uses the legacy contextTypes API which is no longer supported and will be removed'
) !== -1 ||
format.indexOf(
'uses the legacy childContextTypes API which is no longer supported and will be removed'
) !== -1 ||
format.indexOf('ReactDOMTestUtils is deprecated') !== -1 ||
format.indexOf('`ReactDOMTestUtils.act` is deprecated') !== -1 ||
format.indexOf('findDOMNode is deprecated and will be removed') !==
-1 ||
format.indexOf('unmountComponentAtNode is deprecated') !== -1
) {
// This is a backported warning. In `main`, there's a different warning
// (and it's fully tested). Not going to bother upgrading all the tests
// on this old release branch, so let's just silence it instead.
return true;
}
}
} else {
if (