Repository: mui/material-ui (TypeScript)
Date: 2020-04-04 08:59:59+00:00
Task type: Bug Fix

Issue:
Autocomplete immediately changes initial inputValue state
When using Autocomplete as a controlled component and driving that state with onInputChange, the initial state is overwritten immediately to a blank string: `''`. 

<!--
  Thank you very much for contributing to Material-UI by creating an issue! ❤️
  To avoid duplicate issues we ask you to check off the following list.
-->

<!-- Checked checkbox should look like this: [x] -->

- [x] The issue is present in the latest release.
- [x] I have searched the [issues](https://github.com/mui-org/material-ui/issues) of this repository and believe that this is not a duplicate.

## Current Behavior 😯

<!-- Describe what happens instead of the expected behavior. -->
Set up `inputValue` and `onInputChange` to make Autocomplete a controlled component with an initial value not equal to `''`. 
On startup, notice onInputChange is immediately called with a blank string and thus resets the initial value.
## Expected Behavior 🤔
The initial value of `inputValue` is retained.

## Steps to Reproduce 🕹

<!--
  Provide a link to a live example (you can use codesandbox.io) and an unambiguous set of steps to reproduce this bug.
  Include code to reproduce, if relevant (which it most likely is).

  This codesandbox.io template _may_ be a good starting point:
  https://codesandbox.io/s/github/mui-org/material-ui/tree/master/examples/create-react-app

  If you're using typescript a better starting point would be
  https://codesandbox.io/s/github/mui-org/material-ui/tree/master/examples/create-react-app-with-typescript

  If YOU DO NOT take time to provide a codesandbox.io reproduction, should the COMMUNITY take time to help you?
-->

https://codesandbox.io/s/material-demo-2quum?fontsize=14&hidenavigation=1&theme=dark

Steps:

1. Notice that the state is initialized to 'The Shawshank Redemption`
2. Notice on start up that `onInputChange`(console logged) is called on startup, resetting the initial value to `''`

## Context 🔦
The presence of this bug prevents me from using Autocomplete as a controlled component as it doesn't preserve my initial value
<!--
  What are you trying to accomplish? How has this issue affected you?
  Providing context helps us come up with a solution that is most useful in the real world.
-->

## Your Environment 🌎

<!--
  Include as many relevant details about the environment with which you experienced the bug.
  If you encounter issues with typescript please include version and tsconfig.
-->

| Tech        | Version |
| ----------- | ------- |
| Material-UI | v4.9.0  |
| React       | v16.12.0        |
| Browser     |         |
| TypeScript  |         |
| etc.        |         |



Issue discussion:
I have a feeling this goes hand-in-hand with this issue: https://github.com/mui-org/material-ui/issues/19318.
Thanks for opening this issue and providing a codesandbox explaining the issue.

Unfortunately I cannot observe the issue. In codesandbox.io/embed/material-demo-2quum?fontsize=14&hidenavigation=1&theme=dark nothing is logged to the console. Did you accidentally change the sandbox? I would recommend freezing the codesandbox after you opened the issue.

Could you include what browser and operating system you're using? 
I apologize for the confusion - I was having issues with code sandbox saving my changes as you suggested. The posted link should be up to date now. I am on Linux/Chrome, I also tested on Linux/Firefox.
@willwill96 I have seen at least 5 developers fall into this trap so far, e.g. #19318.

There are two isolated states 1. `value => onChange` and 2. `inputValue => onInputChange`. You can't mix the controlled field and the controlled event, they work in pair.
@oliviertassinari Am I misunderstanding something? It seems like you are saying only use `inputValue` with `onInputChange` and only use `value` with `onChange`. In my codesandbox example posted, I only used `inputValue` with `onInputChange`, and still saw the same result of the initial value being reset
👋 Thanks for using Material-UI!

We use GitHub issues exclusively as a bug and feature requests tracker, however,
this issue appears to be a support request.

For support, please check out https://material-ui.com/getting-started/support/. Thanks!

If you have a question on StackOverflow, you are welcome to link to it here, it might help others.
If your issue is subsequently confirmed as a bug,  and the report follows the issue template, it can be reopened.
I can't think of a way we could better teach this (leveraging the documentation and the component warnings). I assume StackOverflow is the best answer we can have (somebody explaining it there). Thanks for raising.
Well, actually, we could always try a more detailed controlled docs section.
It would be nice if one of the demos used `onInputChange` correctly as an example I think. That being said I am unsure how this is not classified as a bug. 

I understand now why the ticket I linked (https://github.com/mui-org/material-ui/issues/19318) is not considered a bug, but the issue I posted here is not the same, and I believe I am using the `Autocomplete` in the proper way. 

The code sandbox posted in this description, uses only  `inputValue => onInputChange` as you suggested in your comment here: https://github.com/mui-org/material-ui/issues/19423#issuecomment-578822292 .  (assuming i understood you correctly)
@willwill96 The value takes precedence over the inputValue. 
@oliviertassinari Why does an undefined `value` prop take precedence over a defined `inputValue` prop?

I am not passing in the `value` or `onChange` prop into my codesandbox. 

I could be mistaken, but I feel that you are addressing the ticket that I linked, and not the issue that I presented in this ticket.
@willwill96 The value is not undefined but null by default, hence an empty input value.
It seems that we could at least improve the API's documentation, in case you want to submit a pull request :)

![Capture d’écran 2020-01-27 à 21 28 39](https://user-images.githubusercontent.com/3165635/73211181-08d94c80-414c-11ea-886e-97f052df6fda.png)

```diff
diff --git a/docs/pages/api/autocomplete.md b/docs/pages/api/autocomplete.md
index f6531069f..e158319ee 100644
--- a/docs/pages/api/autocomplete.md
+++ b/docs/pages/api/autocomplete.md
@@ -35,7 +35,7 @@ You can learn more about the difference by [reading this guide](/guides/minimizi
 | <span class="prop-name">closeIcon</span> | <span class="prop-type">node</span> | <span class="prop-default">&lt;CloseIcon fontSize="small" /></span> | The i
con to display in place of the default close icon. |
 | <span class="prop-name">closeText</span> | <span class="prop-type">string</span> | <span class="prop-default">'Close'</span> | Override the default text for
 the *close popup* icon button.<br>For localization purposes, you can use the provided [translations](/guides/localization/). |
 | <span class="prop-name">debug</span> | <span class="prop-type">bool</span> | <span class="prop-default">false</span> | If `true`, the popup will ignore the
blur event if the input if filled. You can inspect the popup markup with your browser tools. Consider this option when you need to customize the component. |
-| <span class="prop-name">defaultValue</span> | <span class="prop-type">any<br>&#124;&nbsp;array</span> |  | The default input value. Use when the component i
s not controlled. |
+| <span class="prop-name">defaultValue</span> | <span class="prop-type">any<br>&#124;&nbsp;array</span> | <span class="prop-default">props.multiple ? [] : nul
l</span> | The default input value. Use when the component is not controlled. |
 | <span class="prop-name">disableClearable</span> | <span class="prop-type">bool</span> | <span class="prop-default">false</span> | If `true`, the input can't
 be cleared. |
 | <span class="prop-name">disableCloseOnSelect</span> | <span class="prop-type">bool</span> | <span class="prop-default">false</span> | If `true`, the popup w
on't close when a value is selected. |
 | <span class="prop-name">disabled</span> | <span class="prop-type">bool</span> | <span class="prop-default">false</span> | If `true`, the input will be disab
led. |
diff --git a/packages/material-ui-lab/src/Autocomplete/Autocomplete.js b/packages/material-ui-lab/src/Autocomplete/Autocomplete.js
index 5c0ebf1ba..342c8edf0 100644
--- a/packages/material-ui-lab/src/Autocomplete/Autocomplete.js
+++ b/packages/material-ui-lab/src/Autocomplete/Autocomplete.js
@@ -247,7 +247,7 @@ const Autocomplete = React.forwardRef(function Autocomplete(props, ref) {
     closeIcon = <CloseIcon fontSize="small" />,
     closeText = 'Close',
     debug = false,
-    defaultValue,
+    defaultValue = props.multiple ? [] : null,
     disableClearable = false,
     disableCloseOnSelect = false,
     disabled = false,
diff --git a/packages/material-ui-lab/src/useAutocomplete/useAutocomplete.js b/packages/material-ui-lab/src/useAutocomplete/useAutocomplete.js
index 6e5cfcb14..00e7ca020 100644
--- a/packages/material-ui-lab/src/useAutocomplete/useAutocomplete.js
+++ b/packages/material-ui-lab/src/useAutocomplete/useAutocomplete.js
@@ -85,7 +85,7 @@ export default function useAutocomplete(props) {
     blurOnSelect = false,
     clearOnEscape = false,
     debug = false,
-    defaultValue,
+    defaultValue = props.multiple ? [] : null,
     disableClearable = false,
     disableCloseOnSelect = false,
     disableListWrap = false,
@@ -193,7 +193,7 @@ export default function useAutocomplete(props) {

   const [value, setValue] = useControlled({
     controlled: valueProp,
-    default: defaultValue || (multiple ? [] : null),
+    default: defaultValue,
     name: componentName,
   });
```

@oliviertassinari  I have put the PR up for that change, and I do think this helps clarify why the issue is happening and I appreciate the time you've put into this :)

I still think the way that these set of props interact together is slightly convoluted though.

This is the way I have had to write my Autocomplete to get the desired effect:
```
const MyComponent = ()=> {
  const [value, setValue] = useState('myInitialValue')
  return (
    <Autocomplete
      defaultValue={value}
      inputValue={value}
      onInputChange={(_, value: string) => {
        setValue(value)
      }} 
     />
  )
}
```
In this example, it's strange to me that I need to give the autocomplete a defaultValue prop despite it being a controlled component. Especially given that the doc for defaultValue says:
> Use when the component is not controlled

In an ideal world I would be using `value` alongside `onInputChange`, but as you said, those don't work together (although I would argue they should if the component is controlled).
@willwill96 For context, why do you control the component for? (Your code snippet looks great). 
We use Autocomplete as a free text input box that also offers suggestions. We control the component because we have a few buttons at the top of our component that need to reset the values to certain templated values. 

If the snippet above looks good to you, then I think I'm fine closing this issue. I do think it is a little strange for `defaultValue` to be stateful, but I suppose that's not really that big of an issue.
Ok, thanks for the feedback. I think that we could wait a bit, to get more feedback from users before closing the issue. I would suspect that we will have more developers confused. I suspect that a new dedicated section for the controllable use cases would be beneficial and the best resolution.
> In this example, it's strange to me that I need to give the autocomplete a defaultValue prop despite it being a controlled component. Especially given that the doc for defaultValue says...

Additionally, if you pass your Autocomplete a `defaultValue`, you will receive this warning:

<img width="1199" alt="Screen Shot 2020-02-03 at 9 44 34 PM" src="https://user-images.githubusercontent.com/53191729/73717347-65011b00-46ce-11ea-8d1f-97034d8f1c26.png">




Regarding a potential solution, I would propose a diff close to this:

```diff
diff --git a/docs/src/pages/components/autocomplete/autocomplete.md b/docs/src/pages/components/autocomplete/autocomplete.md
index 6ec7d561c..c64ae8d0b 100644
--- a/docs/src/pages/components/autocomplete/autocomplete.md
+++ b/docs/src/pages/components/autocomplete/autocomplete.md
@@ -30,6 +30,15 @@ Choose one country between 248.

 {{"demo": "pages/components/autocomplete/CountrySelect.js"}}

+### Controllable states
+
+The component has two states that can be independently controlled:
+
+1. the "value" state with the `value`/`onChange` props combination.
+2. the "input value" state with the `inputValue`/`onInputChange` props combination.
+
+> ⚠️ Do not try to swap the usage of these props.
+
 ## Free solo

 Set `freeSolo` to true so the textbox can contain any arbitrary value. The prop is designed to cover the primary use case of a search box with suggestions, e.g. Google search.
diff --git a/packages/material-ui-lab/src/useAutocomplete/useAutocomplete.js b/packages/material-ui-lab/src/useAutocomplete/useAutocomplete.js
index 9957ed76d..68bf22375 100644
--- a/packages/material-ui-lab/src/useAutocomplete/useAutocomplete.js
+++ b/packages/material-ui-lab/src/useAutocomplete/useAutocomplete.js
@@ -198,9 +198,12 @@ export default function useAutocomplete(props) {
     name: componentName,
   });

-  const { current: isInputValueControlled } = React.useRef(inputValueProp != null);
-  const [inputValueState, setInputValue] = React.useState('');
-  const inputValue = isInputValueControlled ? inputValueProp : inputValueState;
+  const [inputValue, setInputValue] = useControlled({
+    controlled: inputValueProp,
+    default: '',
+    name: componentName,
+    value: 'inputValue',
+  });

   const [focused, setFocused] = React.useState(false);

diff --git a/packages/material-ui/src/utils/useControlled.js b/packages/material-ui/src/utils/useControlled.js
index 76c455e8c..21851b6cd 100644
--- a/packages/material-ui/src/utils/useControlled.js
+++ b/packages/material-ui/src/utils/useControlled.js
@@ -1,7 +1,7 @@
 /* eslint-disable react-hooks/rules-of-hooks, react-hooks/exhaustive-deps */
 import React from 'react';

-export default function useControlled({ controlled, default: defaultProp, name }) {
+export default function useControlled({ controlled, default: defaultProp, name, state = 'value' }) {
   const { current: isControlled } = React.useRef(controlled !== undefined);
   const [valueState, setValue] = React.useState(defaultProp);
   const value = isControlled ? controlled : valueState;
@@ -11,7 +11,7 @@ export default function useControlled({ controlled, default: defaultProp, name }
       if (isControlled !== (controlled !== undefined)) {
         console.error(
           [
-            `Material-UI: A component is changing ${
+            `Material-UI: A component is changing a ${state} of ${
               isControlled ? 'a ' : 'an un'
             }controlled ${name} to be ${isControlled ? 'un' : ''}controlled.`,
+            'The nature of the state is determined during the first render, it's considered controlled if the value is not `undefined`.',
             'Elements should not switch from uncontrolled to controlled (or vice versa).',
@@ -29,7 +29,7 @@ export default function useControlled({ controlled, default: defaultProp, name }
       if (defaultValue !== defaultProp) {
         console.error(
           [
-            `Material-UI: A component is changing the default value of an uncontrolled ${name} after being initialized. ` +
+            `Material-UI: A component is changing the default ${state} of an uncontrolled ${name} after being initialized. ` +
               `To suppress this warning opt to use a controlled ${name}.`,
           ].join('\n'),
         );
```
I think I'm running into the exact same problem in my application. I created a sandbox to illustrate it (before I realized this issue already existed): https://codesandbox.io/s/loving-ritchie-ulvin
- when using `value`/`onChange`, the state is not updated when entering free text
- when using `inputValue`/`onInputChange`, the component is shown with an initial empty value

@goffioul That's my problem, too. As a workaround I added `onChange` also to the `TextField`.
@Maaartinus Well, that comes with its own set of issues. If I have the same `onChange` handler for both `Autocomplete` and `TextField` and the initial state value the empty string, then there will be an error generated on the first character entered, about the `TextField` switching from uncontrolled to controlled mode.

I also wanted to mention that the autocomplete popup behaves a bit strangely when using `value`/`onChange` (without also setting `onChange` on the `TextField`) and the initial value is part of the available options. In the provided sandbox, `Option 1` is the initial value. On focusing the field, the autocomplete popup contains all options. Erase the `1` and enter `2` instead, only `Option 2` is listed in the autocomplete popup . Erase the `2` and re-enter `1`, all options are again listed in the autocomplete popup. Maybe there's a good reason behind that logic, but I find it confusing.
@goffioul @Maaartinus try this: https://github.com/bugzpodder/ui-components/blob/master/src/common-suggest/common-suggest.tsx#L72
@bugzpodder I have some concerns with the logic in https://github.com/bugzpodder/ui-components/blob/07409c975c8dfea9ea37d7f129a8170abdc7daf1/src/common-suggest/common-suggest.tsx#L72-L81. The `value` and `inputValue` states are meant to be isolated.
As mentioned before about defaultValue, I'm facing the same problem using the `useAutocomplete` hook, there is a way to provide a function in that to setInputValue, or inside of this hooks, put an effect over `inputValue` to detect when the property changes and update the internal state together, because I was trying to do that and see that `useAutocomplete` has an internal state, so would be great set a value whenever I want.

Solution patch:
diff --git a/docs/src/pages/components/autocomplete/autocomplete.md b/docs/src/pages/components/autocomplete/autocomplete.md
--- a/docs/src/pages/components/autocomplete/autocomplete.md
+++ b/docs/src/pages/components/autocomplete/autocomplete.md
@@ -32,6 +32,15 @@ Choose one of the 248 countries.
 
 {{"demo": "pages/components/autocomplete/CountrySelect.js"}}
 
+### Controllable states
+
+The component has two states that can be controlled:
+
+1. the "value" state with the `value`/`onChange` props combination.
+2. the "input value" state with the `inputValue`/`onInputChange` props combination.
+
+> ⚠️ These two state are isolated, they should be controlled independently.
+
 ## Free solo
 
 Set `freeSolo` to true so the textbox can contain any arbitrary value. The prop is designed to cover the primary use case of a search box with suggestions, e.g. Google search.
diff --git a/packages/material-ui-lab/src/Pagination/usePagination.js b/packages/material-ui-lab/src/Pagination/usePagination.js
--- a/packages/material-ui-lab/src/Pagination/usePagination.js
+++ b/packages/material-ui-lab/src/Pagination/usePagination.js
@@ -25,6 +25,7 @@ export default function usePagination(props = {}) {
     controlled: pageProp,
     default: defaultPage,
     name: componentName,
+    state: 'page',
   });
 
   const handleClick = (event, value) => {
diff --git a/packages/material-ui-lab/src/TreeView/TreeView.js b/packages/material-ui-lab/src/TreeView/TreeView.js
--- a/packages/material-ui-lab/src/TreeView/TreeView.js
+++ b/packages/material-ui-lab/src/TreeView/TreeView.js
@@ -67,12 +67,14 @@ const TreeView = React.forwardRef(function TreeView(props, ref) {
     controlled: expandedProp,
     default: defaultExpanded,
     name: 'TreeView',
+    state: 'expanded',
   });
 
   const [selected, setSelectedState] = useControlled({
     controlled: selectedProp,
     default: defaultSelected,
     name: 'TreeView',
+    state: 'selected',
   });
 
   /*
diff --git a/packages/material-ui-lab/src/useAutocomplete/useAutocomplete.js b/packages/material-ui-lab/src/useAutocomplete/useAutocomplete.js
--- a/packages/material-ui-lab/src/useAutocomplete/useAutocomplete.js
+++ b/packages/material-ui-lab/src/useAutocomplete/useAutocomplete.js
@@ -186,10 +186,12 @@ export default function useAutocomplete(props) {
     default: defaultValue,
     name: componentName,
   });
-
-  const { current: isInputValueControlled } = React.useRef(inputValueProp != null);
-  const [inputValueState, setInputValue] = React.useState('');
-  const inputValue = isInputValueControlled ? inputValueProp : inputValueState;
+  const [inputValue, setInputValue] = useControlled({
+    controlled: inputValueProp,
+    default: '',
+    name: componentName,
+    state: 'inputValue',
+  });
 
   const [focused, setFocused] = React.useState(false);
 
diff --git a/packages/material-ui/src/ExpansionPanel/ExpansionPanel.js b/packages/material-ui/src/ExpansionPanel/ExpansionPanel.js
--- a/packages/material-ui/src/ExpansionPanel/ExpansionPanel.js
+++ b/packages/material-ui/src/ExpansionPanel/ExpansionPanel.js
@@ -99,6 +99,7 @@ const ExpansionPanel = React.forwardRef(function ExpansionPanel(props, ref) {
     controlled: expandedProp,
     default: defaultExpanded,
     name: 'ExpansionPanel',
+    state: 'expanded',
   });
 
   const handleChange = React.useCallback(
diff --git a/packages/material-ui/src/Tooltip/Tooltip.js b/packages/material-ui/src/Tooltip/Tooltip.js
--- a/packages/material-ui/src/Tooltip/Tooltip.js
+++ b/packages/material-ui/src/Tooltip/Tooltip.js
@@ -215,6 +215,7 @@ const Tooltip = React.forwardRef(function Tooltip(props, ref) {
     controlled: openProp,
     default: false,
     name: 'Tooltip',
+    state: 'open',
   });
   let open = openState;
 
diff --git a/packages/material-ui/src/internal/SwitchBase.js b/packages/material-ui/src/internal/SwitchBase.js
--- a/packages/material-ui/src/internal/SwitchBase.js
+++ b/packages/material-ui/src/internal/SwitchBase.js
@@ -58,6 +58,7 @@ const SwitchBase = React.forwardRef(function SwitchBase(props, ref) {
     controlled: checkedProp,
     default: Boolean(defaultChecked),
     name: 'SwitchBase',
+    state: 'checked',
   });
 
   const muiFormControl = useFormControl();
diff --git a/packages/material-ui/src/utils/useControlled.js b/packages/material-ui/src/utils/useControlled.js
--- a/packages/material-ui/src/utils/useControlled.js
+++ b/packages/material-ui/src/utils/useControlled.js
@@ -1,7 +1,7 @@
 /* eslint-disable react-hooks/rules-of-hooks, react-hooks/exhaustive-deps */
 import * as React from 'react';
 
-export default function useControlled({ controlled, default: defaultProp, name }) {
+export default function useControlled({ controlled, default: defaultProp, name, state = 'value' }) {
   const { current: isControlled } = React.useRef(controlled !== undefined);
   const [valueState, setValue] = React.useState(defaultProp);
   const value = isControlled ? controlled : valueState;
@@ -11,12 +11,13 @@ export default function useControlled({ controlled, default: defaultProp, name }
       if (isControlled !== (controlled !== undefined)) {
         console.error(
           [
-            `Material-UI: A component is changing ${
-              isControlled ? 'a ' : 'an un'
-            }controlled ${name} to be ${isControlled ? 'un' : ''}controlled.`,
+            `Material-UI: a component is changing the ${
+              isControlled ? '' : 'un'
+            }controlled ${state} state of ${name} to be ${isControlled ? 'un' : ''}controlled.`,
             'Elements should not switch from uncontrolled to controlled (or vice versa).',
             `Decide between using a controlled or uncontrolled ${name} ` +
               'element for the lifetime of the component.',
+            "The nature of the state is determined during the first render, it's considered controlled if the value is not `undefined`.",
             'More info: https://fb.me/react-controlled-components',
           ].join('\n'),
         );
@@ -29,7 +30,7 @@ export default function useControlled({ controlled, default: defaultProp, name }
       if (defaultValue !== defaultProp) {
         console.error(
           [
-            `Material-UI: A component is changing the default value of an uncontrolled ${name} after being initialized. ` +
+            `Material-UI: a component is changing the default ${state} state of an uncontrolled ${name} after being initialized. ` +
               `To suppress this warning opt to use a controlled ${name}.`,
           ].join('\n'),
         );


Test patch:
diff --git a/packages/material-ui-lab/src/TreeView/TreeView.test.js b/packages/material-ui-lab/src/TreeView/TreeView.test.js
--- a/packages/material-ui-lab/src/TreeView/TreeView.test.js
+++ b/packages/material-ui-lab/src/TreeView/TreeView.test.js
@@ -46,7 +46,7 @@ describe('<TreeView />', () => {
 
       setProps({ expanded: undefined });
       expect(consoleErrorMock.messages()[0]).to.include(
-        'A component is changing a controlled TreeView to be uncontrolled',
+        'Material-UI: a component is changing the controlled expanded state of TreeView to be uncontrolled.',
       );
     });
 
@@ -59,7 +59,7 @@ describe('<TreeView />', () => {
 
       setProps({ selected: undefined });
       expect(consoleErrorMock.messages()[0]).to.include(
-        'A component is changing a controlled TreeView to be uncontrolled',
+        'Material-UI: a component is changing the controlled selected state of TreeView to be uncontrolled.',
       );
     });
   });
diff --git a/packages/material-ui/src/ExpansionPanel/ExpansionPanel.test.js b/packages/material-ui/src/ExpansionPanel/ExpansionPanel.test.js
--- a/packages/material-ui/src/ExpansionPanel/ExpansionPanel.test.js
+++ b/packages/material-ui/src/ExpansionPanel/ExpansionPanel.test.js
@@ -1,6 +1,6 @@
 import * as React from 'react';
 import PropTypes from 'prop-types';
-import { assert } from 'chai';
+import { assert, expect } from 'chai';
 import { spy } from 'sinon';
 import { createMount, getClasses, findOutermostIntrinsic } from '@material-ui/core/test-utils';
 import describeConformance from '../test-utils/describeConformance';
@@ -193,9 +193,8 @@ describe('<ExpansionPanel />', () => {
       const wrapper = mount(<ExpansionPanel expanded>{minimalChildren}</ExpansionPanel>);
 
       wrapper.setProps({ expanded: undefined });
-      assert.include(
-        consoleErrorMock.messages()[0],
-        'A component is changing a controlled ExpansionPanel to be uncontrolled.',
+      expect(consoleErrorMock.messages()[0]).to.include(
+        'Material-UI: a component is changing the controlled expanded state of ExpansionPanel to be uncontrolled.',
       );
     });
 
@@ -205,7 +204,7 @@ describe('<ExpansionPanel />', () => {
       wrapper.setProps({ expanded: true });
       assert.include(
         consoleErrorMock.messages()[0],
-        'A component is changing an uncontrolled ExpansionPanel to be controlled.',
+        'Material-UI: a component is changing the uncontrolled expanded state of ExpansionPanel to be controlled.',
       );
     });
   });
diff --git a/packages/material-ui/src/RadioGroup/RadioGroup.test.js b/packages/material-ui/src/RadioGroup/RadioGroup.test.js
--- a/packages/material-ui/src/RadioGroup/RadioGroup.test.js
+++ b/packages/material-ui/src/RadioGroup/RadioGroup.test.js
@@ -348,9 +348,8 @@ describe('<RadioGroup />', () => {
       );
 
       wrapper.setProps({ value: undefined });
-      assert.include(
-        consoleErrorMock.messages()[0],
-        'A component is changing a controlled RadioGroup to be uncontrolled.',
+      expect(consoleErrorMock.messages()[0]).to.include(
+        'Material-UI: a component is changing the controlled value state of RadioGroup to be uncontrolled.',
       );
     });
 
@@ -362,9 +361,8 @@ describe('<RadioGroup />', () => {
       );
 
       wrapper.setProps({ value: 'foo' });
-      assert.include(
-        consoleErrorMock.messages()[0],
-        'A component is changing an uncontrolled RadioGroup to be controlled.',
+      expect(consoleErrorMock.messages()[0]).to.include(
+        'Material-UI: a component is changing the uncontrolled value state of RadioGroup to be controlled.',
       );
     });
   });
diff --git a/packages/material-ui/src/Slider/Slider.test.js b/packages/material-ui/src/Slider/Slider.test.js
--- a/packages/material-ui/src/Slider/Slider.test.js
+++ b/packages/material-ui/src/Slider/Slider.test.js
@@ -515,7 +515,7 @@ describe('<Slider />', () => {
 
       setProps({ value: undefined });
       expect(consoleErrorMock.messages()[0]).to.include(
-        'A component is changing a controlled Slider to be uncontrolled.',
+        'Material-UI: a component is changing the controlled value state of Slider to be uncontrolled.',
       );
     });
 
@@ -524,7 +524,7 @@ describe('<Slider />', () => {
 
       setProps({ value: [20, 50] });
       expect(consoleErrorMock.messages()[0]).to.include(
-        'A component is changing an uncontrolled Slider to be controlled.',
+        'Material-UI: a component is changing the uncontrolled value state of Slider to be controlled.',
       );
     });
   });
diff --git a/packages/material-ui/src/Tooltip/Tooltip.test.js b/packages/material-ui/src/Tooltip/Tooltip.test.js
--- a/packages/material-ui/src/Tooltip/Tooltip.test.js
+++ b/packages/material-ui/src/Tooltip/Tooltip.test.js
@@ -522,9 +522,8 @@ describe('<Tooltip />', () => {
       const wrapper = mount(<Tooltip {...defaultProps} />);
 
       wrapper.setProps({ open: true });
-      assert.include(
-        consoleErrorMock.messages()[0],
-        'A component is changing an uncontrolled Tooltip to be controlled.',
+      expect(consoleErrorMock.messages()[0]).to.include(
+        'Material-UI: a component is changing the uncontrolled open state of Tooltip to be controlled.',
       );
     });
   });
diff --git a/packages/material-ui/src/internal/SwitchBase.test.js b/packages/material-ui/src/internal/SwitchBase.test.js
--- a/packages/material-ui/src/internal/SwitchBase.test.js
+++ b/packages/material-ui/src/internal/SwitchBase.test.js
@@ -374,10 +374,10 @@ describe('<SwitchBase />', () => {
         wrapper.setProps({ checked: true });
         expect(consoleErrorMock.callCount()).to.equal(2);
         expect(consoleErrorMock.messages()[0]).to.include(
-          'A component is changing an uncontrolled input of type checkbox to be controlled.',
+          'Warning: A component is changing an uncontrolled input of type checkbox to be controlled.',
         );
         expect(consoleErrorMock.messages()[1]).to.include(
-          'A component is changing an uncontrolled SwitchBase to be controlled.',
+          'Material-UI: a component is changing the uncontrolled checked state of SwitchBase to be controlled.',
         );
       }),
     );
@@ -393,10 +393,10 @@ describe('<SwitchBase />', () => {
         setProps({ checked: undefined });
         expect(consoleErrorMock.callCount()).to.equal(2);
         expect(consoleErrorMock.messages()[0]).to.include(
-          'A component is changing a controlled input of type checkbox to be uncontrolled.',
+          'Warning: A component is changing a controlled input of type checkbox to be uncontrolled.',
         );
         expect(consoleErrorMock.messages()[1]).to.include(
-          'A component is changing a controlled SwitchBase to be uncontrolled.',
+          'Material-UI: a component is changing the controlled checked state of SwitchBase to be uncontrolled.',
         );
       }),
     );
diff --git a/packages/material-ui/src/utils/useControlled.test.js b/packages/material-ui/src/utils/useControlled.test.js
--- a/packages/material-ui/src/utils/useControlled.test.js
+++ b/packages/material-ui/src/utils/useControlled.test.js
@@ -59,8 +59,8 @@ describe('useControlled', () => {
     expect(consoleErrorMock.callCount()).to.equal(0);
     setProps({ value: 'foobar' });
     expect(consoleErrorMock.callCount()).to.equal(1);
-    expect(consoleErrorMock.messages()[0]).to.contains(
-      'A component is changing an uncontrolled TestComponent to be controlled.',
+    expect(consoleErrorMock.messages()[0]).to.include(
+      'Material-UI: a component is changing the uncontrolled value state of TestComponent to be controlled.',
     );
   });
 
@@ -69,8 +69,8 @@ describe('useControlled', () => {
     expect(consoleErrorMock.callCount()).to.equal(0);
     setProps({ value: undefined });
     expect(consoleErrorMock.callCount()).to.equal(1);
-    expect(consoleErrorMock.messages()[0]).to.contains(
-      'A component is changing a controlled TestComponent to be uncontrolled.',
+    expect(consoleErrorMock.messages()[0]).to.include(
+      'Material-UI: a component is changing the controlled value state of TestComponent to be uncontrolled.',
     );
   });
 
@@ -79,8 +79,8 @@ describe('useControlled', () => {
     expect(consoleErrorMock.callCount()).to.equal(0);
     setProps({ defaultValue: 1 });
     expect(consoleErrorMock.callCount()).to.equal(1);
-    expect(consoleErrorMock.messages()[0]).to.contains(
-      'A component is changing the default value of an uncontrolled TestComponent after being initialized. ',
+    expect(consoleErrorMock.messages()[0]).to.include(
+      'Material-UI: a component is changing the default value state of an uncontrolled TestComponent after being initialized.',
     );
   });
 });