Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make Warning for Uncontrolled/Controlled Inputs Clearer #17390

Closed
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, {PureComponent} from 'react';
import {flushSync, unstable_createRoot} from 'react-dom';
import {flushSync, createRoot} from 'react-dom';
import Scheduler from 'scheduler';
import _ from 'lodash';
import Charts from './Charts';
Expand Down Expand Up @@ -107,9 +107,10 @@ class App extends PureComponent {
this.debouncedHandleChange(value);
break;
case 'async':
unstable_scheduleCallback(() => {
// TODO: useTransition hook instead.
setTimeout(() => {
this.setState({value});
});
}, 0);
break;
default:
break;
Expand Down Expand Up @@ -146,5 +147,5 @@ class App extends PureComponent {
}

const container = document.getElementById('root');
const root = ReactDOM.unstable_createRoot(container);
root.render(<App />, container);
const root = createRoot(container);
root.render(<App />);
4 changes: 2 additions & 2 deletions fixtures/ssr/src/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from 'react';
import {unstable_createRoot} from 'react-dom';
import {createRoot} from 'react-dom';

import App from './components/App';

let root = unstable_createRoot(document, {hydrate: true});
let root = createRoot(document, {hydrate: true});
root.render(<App assets={window.assetManifest} />);
37 changes: 36 additions & 1 deletion fixtures/ssr/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2999,6 +2999,11 @@ js-tokens@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.1.tgz#08e9f132484a2c45a30907e9dc4d5567b7f114d7"

"js-tokens@^3.0.0 || ^4.0.0":
version "4.0.0"
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==

js-yaml@^3.4.3, js-yaml@^3.5.1, js-yaml@^3.7.0, js-yaml@~3.7.0:
version "3.7.0"
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.7.0.tgz#5c967ddd837a9bfdca5f2de84253abe8a1c03b80"
Expand Down Expand Up @@ -3248,6 +3253,13 @@ loose-envify@^1.0.0:
dependencies:
js-tokens "^3.0.0"

loose-envify@^1.1.0, loose-envify@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==
dependencies:
js-tokens "^3.0.0 || ^4.0.0"

lower-case@^1.1.1:
version "1.1.4"
resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-1.1.4.tgz#9a2cabd1b9e8e0ae993a4bf7d5875c39c42e8eac"
Expand Down Expand Up @@ -3558,9 +3570,10 @@ oauth-sign@~0.8.1:
version "0.8.2"
resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.8.2.tgz#46a6ab7f0aead8deae9ec0565780b7d4efeb9d43"

object-assign@4.1.1, object-assign@^4.0.1, object-assign@^4.1.0:
object-assign@4.1.1, object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=

object.omit@^2.0.0:
version "2.0.1"
Expand Down Expand Up @@ -4071,6 +4084,15 @@ promise@7.1.1:
dependencies:
asap "~2.0.3"

prop-types@^15.6.2:
version "15.7.2"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5"
integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==
dependencies:
loose-envify "^1.4.0"
object-assign "^4.1.1"
react-is "^16.8.1"

proxy-addr@~1.1.3:
version "1.1.4"
resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-1.1.4.tgz#27e545f6960a44a627d9b44467e35c1b6b4ce2f3"
Expand Down Expand Up @@ -4160,6 +4182,11 @@ react-dev-utils@^0.5.2:
version "0.0.0"
uid ""

react-is@^16.8.1:
version "16.12.0"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.12.0.tgz#2cc0fe0fba742d97fd527c42a13bec4eeb06241c"
integrity sha512-rPCkf/mWBtKc97aLL9/txD8DZdemK0vkA3JMLShjlJB3Pj3s+lpf1KaBzMfQrAmhMQB0n1cU/SUGgKKBCe837Q==

react-scripts@0.9.5:
version "0.9.5"
resolved "https://registry.yarnpkg.com/react-scripts/-/react-scripts-0.9.5.tgz#e9f05c8427e27131662a9b9d7a9786d1ff16bb3f"
Expand Down Expand Up @@ -4495,6 +4522,14 @@ sax@^1.2.1, sax@~1.2.1:
version "1.2.2"
resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.2.tgz#fd8631a23bc7826bef5d871bdb87378c95647828"

scheduler@^0.18.0:
version "0.18.0"
resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.18.0.tgz#5901ad6659bc1d8f3fdaf36eb7a67b0d6746b1c4"
integrity sha512-agTSHR1Nbfi6ulI0kYNK0203joW2Y5W4po4l+v03tOoiJKpTBbxpNhWDvqc/4IcOw+KLmSiQLTasZ4cab2/UWQ==
dependencies:
loose-envify "^1.1.0"
object-assign "^4.1.1"

"semver@2 || 3 || 4 || 5", semver@^5.1.0, semver@^5.3.0:
version "5.3.0"
resolved "https://registry.yarnpkg.com/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f"
Expand Down
18 changes: 8 additions & 10 deletions fixtures/tracing/test.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
const {createElement, Component, Placeholder} = React;
const {unstable_createRoot: createRoot} = ReactDOM;
const {createElement, Component, Suspense} = React;
const {createRoot} = ReactDOM;
const {
unstable_subscribe: subscribe,
unstable_trace: trace,
Expand Down Expand Up @@ -56,8 +56,8 @@ const read = key => {

const TestApp = () =>
createElement(
Placeholder,
{delayMs: 100, fallback: createElement(PlaceholderText)},
Suspense,
{fallback: createElement(PlaceholderText)},
createElement(SuspendingChild, {text: 'foo'}),
createElement(SuspendingChild, {text: 'bar'}),
createElement(SuspendingChild, {text: 'baz'})
Expand Down Expand Up @@ -91,13 +91,11 @@ subscribe({
const element = document.getElementById('root');
trace('initial_render', performance.now(), () => {
const root = createRoot(element);
const batch = root.createBatch();
log.app('batch.render()');
batch.render(createElement(TestApp));
batch.then(
log.app('render()');
root.render(
createElement(TestApp),
wrap(() => {
log.app('batch.commit()');
batch.commit();
log.app('commited');
})
);
});
12 changes: 7 additions & 5 deletions packages/react-dom/src/__tests__/ReactDOMComponentTree-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -178,12 +178,14 @@ describe('ReactDOMComponentTree', () => {
const component = <Controlled />;
const instance = ReactDOM.render(component, container);
expect(() => simulateInput(instance.a, finishValue)).toWarnDev(
'Warning: A component is changing an uncontrolled input of ' +
'type text to be controlled. Input elements should not ' +
'switch from uncontrolled to controlled (or vice versa). ' +
'Warning: A component is changing an uncontrolled input of type text to be controlled. ' +
"This can occur when an input's value changes from undefined to a defined value. " +
'Input elements should not switch from uncontrolled to controlled (or vice versa). ' +
'Decide between using a controlled or uncontrolled input ' +
'element for the lifetime of the component. More info: ' +
'https://fb.me/react-controlled-components',
'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' +
' in input (at **)' +
'\n' +
' in Controlled (at **)',
);
});

Expand Down
16 changes: 16 additions & 0 deletions packages/react-dom/src/__tests__/ReactDOMInput-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1269,6 +1269,7 @@ describe('ReactDOMInput', () => {
ReactDOM.render(stub, container);
expect(() => ReactDOM.render(<input type="text" />, container)).toWarnDev(
'Warning: A component is changing a controlled input of type text to be uncontrolled. ' +
"This can occur when an input's value changes from a defined value to undefined. " +
'Input elements should not switch from controlled to uncontrolled (or vice versa). ' +
'Decide between using a controlled or uncontrolled input ' +
'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' +
Expand All @@ -1287,6 +1288,7 @@ describe('ReactDOMInput', () => {
'`value` prop on `input` should not be null. ' +
'Consider using an empty string to clear the component or `undefined` for uncontrolled components',
'Warning: A component is changing a controlled input of type text to be uncontrolled. ' +
"This can occur when an input's value changes from a defined value to undefined. " +
'Input elements should not switch from controlled to uncontrolled (or vice versa). ' +
'Decide between using a controlled or uncontrolled input ' +
'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' +
Expand All @@ -1306,6 +1308,7 @@ describe('ReactDOMInput', () => {
),
).toWarnDev(
'Warning: A component is changing a controlled input of type text to be uncontrolled. ' +
"This can occur when an input's value changes from a defined value to undefined. " +
'Input elements should not switch from controlled to uncontrolled (or vice versa). ' +
'Decide between using a controlled or uncontrolled input ' +
'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' +
Expand All @@ -1320,6 +1323,7 @@ describe('ReactDOMInput', () => {
ReactDOM.render(<input type="text" value="controlled" />, container),
).toWarnDev(
'Warning: A component is changing an uncontrolled input of type text to be controlled. ' +
"This can occur when an input's value changes from undefined to a defined value. " +
'Input elements should not switch from uncontrolled to controlled (or vice versa). ' +
'Decide between using a controlled or uncontrolled input ' +
'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' +
Expand All @@ -1337,6 +1341,7 @@ describe('ReactDOMInput', () => {
ReactDOM.render(<input type="text" value="controlled" />, container),
).toWarnDev(
'Warning: A component is changing an uncontrolled input of type text to be controlled. ' +
"This can occur when an input's value changes from undefined to a defined value. " +
'Input elements should not switch from uncontrolled to controlled (or vice versa). ' +
'Decide between using a controlled or uncontrolled input ' +
'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' +
Expand All @@ -1353,6 +1358,7 @@ describe('ReactDOMInput', () => {
ReactDOM.render(<input type="checkbox" />, container),
).toWarnDev(
'Warning: A component is changing a controlled input of type checkbox to be uncontrolled. ' +
"This can occur when an input's value changes from a defined value to undefined. " +
'Input elements should not switch from controlled to uncontrolled (or vice versa). ' +
'Decide between using a controlled or uncontrolled input ' +
'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' +
Expand All @@ -1369,6 +1375,7 @@ describe('ReactDOMInput', () => {
ReactDOM.render(<input type="checkbox" checked={null} />, container),
).toWarnDev(
'Warning: A component is changing a controlled input of type checkbox to be uncontrolled. ' +
"This can occur when an input's value changes from a defined value to undefined. " +
'Input elements should not switch from controlled to uncontrolled (or vice versa). ' +
'Decide between using a controlled or uncontrolled input ' +
'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' +
Expand All @@ -1388,6 +1395,7 @@ describe('ReactDOMInput', () => {
),
).toWarnDev(
'Warning: A component is changing a controlled input of type checkbox to be uncontrolled. ' +
"This can occur when an input's value changes from a defined value to undefined. " +
'Input elements should not switch from controlled to uncontrolled (or vice versa). ' +
'Decide between using a controlled or uncontrolled input ' +
'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' +
Expand All @@ -1402,6 +1410,7 @@ describe('ReactDOMInput', () => {
ReactDOM.render(<input type="checkbox" checked={true} />, container),
).toWarnDev(
'Warning: A component is changing an uncontrolled input of type checkbox to be controlled. ' +
"This can occur when an input's value changes from undefined to a defined value. " +
'Input elements should not switch from uncontrolled to controlled (or vice versa). ' +
'Decide between using a controlled or uncontrolled input ' +
'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' +
Expand All @@ -1416,6 +1425,7 @@ describe('ReactDOMInput', () => {
ReactDOM.render(<input type="checkbox" checked={true} />, container),
).toWarnDev(
'Warning: A component is changing an uncontrolled input of type checkbox to be controlled. ' +
"This can occur when an input's value changes from undefined to a defined value. " +
'Input elements should not switch from uncontrolled to controlled (or vice versa). ' +
'Decide between using a controlled or uncontrolled input ' +
'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' +
Expand All @@ -1428,6 +1438,7 @@ describe('ReactDOMInput', () => {
ReactDOM.render(stub, container);
expect(() => ReactDOM.render(<input type="radio" />, container)).toWarnDev(
'Warning: A component is changing a controlled input of type radio to be uncontrolled. ' +
"This can occur when an input's value changes from a defined value to undefined. " +
'Input elements should not switch from controlled to uncontrolled (or vice versa). ' +
'Decide between using a controlled or uncontrolled input ' +
'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' +
Expand All @@ -1442,6 +1453,7 @@ describe('ReactDOMInput', () => {
ReactDOM.render(<input type="radio" checked={null} />, container),
).toWarnDev(
'Warning: A component is changing a controlled input of type radio to be uncontrolled. ' +
"This can occur when an input's value changes from a defined value to undefined. " +
'Input elements should not switch from controlled to uncontrolled (or vice versa). ' +
'Decide between using a controlled or uncontrolled input ' +
'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' +
Expand All @@ -1456,6 +1468,7 @@ describe('ReactDOMInput', () => {
ReactDOM.render(<input type="radio" defaultChecked={true} />, container),
).toWarnDev(
'Warning: A component is changing a controlled input of type radio to be uncontrolled. ' +
"This can occur when an input's value changes from a defined value to undefined. " +
'Input elements should not switch from controlled to uncontrolled (or vice versa). ' +
'Decide between using a controlled or uncontrolled input ' +
'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' +
Expand All @@ -1470,6 +1483,7 @@ describe('ReactDOMInput', () => {
ReactDOM.render(<input type="radio" checked={true} />, container),
).toWarnDev(
'Warning: A component is changing an uncontrolled input of type radio to be controlled. ' +
"This can occur when an input's value changes from undefined to a defined value. " +
'Input elements should not switch from uncontrolled to controlled (or vice versa). ' +
'Decide between using a controlled or uncontrolled input ' +
'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' +
Expand All @@ -1484,6 +1498,7 @@ describe('ReactDOMInput', () => {
ReactDOM.render(<input type="radio" checked={true} />, container),
).toWarnDev(
'Warning: A component is changing an uncontrolled input of type radio to be controlled. ' +
"This can occur when an input's value changes from undefined to a defined value. " +
'Input elements should not switch from uncontrolled to controlled (or vice versa). ' +
'Decide between using a controlled or uncontrolled input ' +
'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' +
Expand Down Expand Up @@ -1536,6 +1551,7 @@ describe('ReactDOMInput', () => {
ReactDOM.render(<input type="radio" value="value" />, container),
).toWarnDev(
'Warning: A component is changing a controlled input of type radio to be uncontrolled. ' +
"This can occur when an input's value changes from a defined value to undefined. " +
'Input elements should not switch from controlled to uncontrolled (or vice versa). ' +
'Decide between using a controlled or uncontrolled input ' +
'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' +
Expand Down
2 changes: 2 additions & 0 deletions packages/react-dom/src/client/ReactDOMInput.js
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ export function updateWrapper(element: Element, props: Object) {
warning(
false,
'A component is changing an uncontrolled input of type %s to be controlled. ' +
"This can occur when an input's value changes from undefined to a defined value. " +
'Input elements should not switch from uncontrolled to controlled (or vice versa). ' +
'Decide between using a controlled or uncontrolled input ' +
'element for the lifetime of the component. More info: https://fb.me/react-controlled-components',
Expand All @@ -161,6 +162,7 @@ export function updateWrapper(element: Element, props: Object) {
warning(
false,
'A component is changing a controlled input of type %s to be uncontrolled. ' +
"This can occur when an input's value changes from a defined value to undefined. " +
'Input elements should not switch from controlled to uncontrolled (or vice versa). ' +
'Decide between using a controlled or uncontrolled input ' +
'element for the lifetime of the component. More info: https://fb.me/react-controlled-components',
Expand Down