Repository: mui/material-ui (TypeScript)
Date: 2019-07-18 12:54:24+00:00
Task type: Bug Fix

Issue:
TextareaAutosize infinite render loop

I think the infinite render loop bug in #16387, #10717, #16222 may still exist in the new `TextareaAutosize` component, which is possibly related to Chrome in Win10 when scaled to 125% displaying a scrollbar.

### [CodeSandbox Demo ](https://codesandbox.io/s/boring-ride-9h9vx)

OS: Win10
MUI version: 4.2.0


Issue discussion:
@ZYinMD I think that I have found the source of the issue. We compare inner height with outer height to determine if the hidden style should be applied. The issue is that these two values are not always in the same referential. They are not with a padding value and box sizing = content box. Can you confirm that this diff fixes the issue?
```diff
diff --git a/packages/material-ui/src/TextareaAutosize/TextareaAutosize.js b/packages/material-ui/src/TextareaAutosize/TextareaAutosize.js
index 4a0cf1d99..01bc9ad83 100644
--- a/packages/material-ui/src/TextareaAutosize/TextareaAutosize.js
+++ b/packages/material-ui/src/TextareaAutosize/TextareaAutosize.js
@@ -47,7 +47,7 @@ const TextareaAutosize = React.forwardRef(function TextareaAutosize(props, ref)
       getStyleValue(computedStyle, 'border-top-width');

     // The height of the inner content
-    const innerHeight = inputShallow.scrollHeight;
+    const innerHeight = inputShallow.scrollHeight - padding;

     // Measure height of a textarea with a single row
     inputShallow.value = 'x';
@@ -55,7 +55,7 @@ const TextareaAutosize = React.forwardRef(function TextareaAutosize(props, ref)
     singleRowHeight -= padding;

     // The height of the outer content
-    let outerHeight = innerHeight - padding;
+    let outerHeight = innerHeight;

     if (rows != null) {
       outerHeight = Math.max(Number(rows) * singleRowHeight, outerHeight);
@@ -65,15 +65,17 @@ const TextareaAutosize = React.forwardRef(function TextareaAutosize(props, ref)
     }
     outerHeight = Math.max(outerHeight, singleRowHeight);

-    outerHeight += boxSizing === 'border-box' ? padding + border : 0;
+    // Take the box sizing into account for applying this value as a style.
+    const outerHeightStyle = outerHeight + boxSizing === 'border-box' ? padding + border : 0;

     setState(prevState => {
       // Need a large enough different to update the height.
       // This prevents infinite rendering loop.
-      if (outerHeight > 0 && Math.abs((prevState.outerHeight || 0) - outerHeight) > 1) {
+      if (outerHeightStyle > 0 && Math.abs((prevState.outerHeightStyle || 0) - outerHeightStyle) > 1) {
         return {
           innerHeight,
           outerHeight,
+          outerHeightStyle,
         };
       }

@@ -116,7 +118,7 @@ const TextareaAutosize = React.forwardRef(function TextareaAutosize(props, ref)
         // Apply the rows prop to get a "correct" first SSR paint
         rows={rows || 1}
         style={{
-          height: state.outerHeight,
+          height: state.outerHeightStyle,
           // Need a large enough different to allow scrolling.
           // This prevents infinite rendering loop.
           overflow: Math.abs(state.outerHeight - state.innerHeight) <= 1 ? 'hidden' : null,
```
Hi @oliviertassinari, I confirm that after pasting your changes, the bug disappears. However, the `TextareaAutosize` becomes fixed height, and no longer auto grows.

Btw, on an irrelevant note: in v4.2.0, named export of `TextareaAutosize ` doesn't work:
`import { TextareaAutosize } from '@material-ui/core';`
and import must be done this way:
`import TextareaAutosize from '@material-ui/core/TextareaAutosize';`

Solution patch:
diff --git a/packages/material-ui/src/TextareaAutosize/TextareaAutosize.js b/packages/material-ui/src/TextareaAutosize/TextareaAutosize.js
--- a/packages/material-ui/src/TextareaAutosize/TextareaAutosize.js
+++ b/packages/material-ui/src/TextareaAutosize/TextareaAutosize.js
@@ -47,15 +47,14 @@ const TextareaAutosize = React.forwardRef(function TextareaAutosize(props, ref)
       getStyleValue(computedStyle, 'border-top-width');
 
     // The height of the inner content
-    const innerHeight = inputShallow.scrollHeight;
+    const innerHeight = inputShallow.scrollHeight - padding;
 
     // Measure height of a textarea with a single row
     inputShallow.value = 'x';
-    let singleRowHeight = inputShallow.scrollHeight;
-    singleRowHeight -= padding;
+    const singleRowHeight = inputShallow.scrollHeight - padding;
 
     // The height of the outer content
-    let outerHeight = innerHeight - padding;
+    let outerHeight = innerHeight;
 
     if (rows != null) {
       outerHeight = Math.max(Number(rows) * singleRowHeight, outerHeight);
@@ -65,15 +64,20 @@ const TextareaAutosize = React.forwardRef(function TextareaAutosize(props, ref)
     }
     outerHeight = Math.max(outerHeight, singleRowHeight);
 
-    outerHeight += boxSizing === 'border-box' ? padding + border : 0;
+    // Take the box sizing into account for applying this value as a style.
+    const outerHeightStyle = outerHeight + (boxSizing === 'border-box' ? padding + border : 0);
 
     setState(prevState => {
       // Need a large enough different to update the height.
       // This prevents infinite rendering loop.
-      if (outerHeight > 0 && Math.abs((prevState.outerHeight || 0) - outerHeight) > 1) {
+      if (
+        outerHeightStyle > 0 &&
+        Math.abs((prevState.outerHeightStyle || 0) - outerHeightStyle) > 1
+      ) {
         return {
           innerHeight,
           outerHeight,
+          outerHeightStyle,
         };
       }
 
@@ -116,7 +120,7 @@ const TextareaAutosize = React.forwardRef(function TextareaAutosize(props, ref)
         // Apply the rows prop to get a "correct" first SSR paint
         rows={rows || 1}
         style={{
-          height: state.outerHeight,
+          height: state.outerHeightStyle,
           // Need a large enough different to allow scrolling.
           // This prevents infinite rendering loop.
           overflow: Math.abs(state.outerHeight - state.innerHeight) <= 1 ? 'hidden' : null,


Test patch:
diff --git a/packages/material-ui/src/TextareaAutosize/TextareaAutosize.test.js b/packages/material-ui/src/TextareaAutosize/TextareaAutosize.test.js
--- a/packages/material-ui/src/TextareaAutosize/TextareaAutosize.test.js
+++ b/packages/material-ui/src/TextareaAutosize/TextareaAutosize.test.js
@@ -5,11 +5,11 @@ import { createMount } from '@material-ui/core/test-utils';
 import describeConformance from '@material-ui/core/test-utils/describeConformance';
 import TextareaAutosize from './TextareaAutosize';
 
-function getHeight(wrapper) {
+function getStyle(wrapper) {
   return wrapper
     .find('textarea')
     .at(0)
-    .props().style.height;
+    .props().style;
 }
 
 describe('<TextareaAutosize />', () => {
@@ -78,7 +78,10 @@ describe('<TextareaAutosize />', () => {
 
       it('should handle the resize event', () => {
         const wrapper = mount(<TextareaAutosize />);
-        assert.strictEqual(getHeight(wrapper), undefined);
+        assert.deepEqual(getStyle(wrapper), {
+          height: undefined,
+          overflow: null,
+        });
         setLayout(wrapper, {
           getComputedStyle: {
             'box-sizing': 'content-box',
@@ -89,14 +92,17 @@ describe('<TextareaAutosize />', () => {
         window.dispatchEvent(new window.Event('resize', {}));
         clock.tick(166);
         wrapper.update();
-        assert.strictEqual(getHeight(wrapper), 30);
+        assert.deepEqual(getStyle(wrapper), {
+          height: 30,
+          overflow: 'hidden',
+        });
       });
     });
 
     it('should update when uncontrolled', () => {
       const handleChange = spy();
       const wrapper = mount(<TextareaAutosize onChange={handleChange} />);
-      assert.strictEqual(getHeight(wrapper), undefined);
+      assert.deepEqual(getStyle(wrapper), { height: undefined, overflow: null });
       setLayout(wrapper, {
         getComputedStyle: {
           'box-sizing': 'content-box',
@@ -109,14 +115,14 @@ describe('<TextareaAutosize />', () => {
         .at(0)
         .simulate('change');
       wrapper.update();
-      assert.strictEqual(getHeight(wrapper), 30);
+      assert.deepEqual(getStyle(wrapper), { height: 30, overflow: 'hidden' });
       assert.strictEqual(handleChange.callCount, 1);
     });
 
     it('should take the border into account with border-box', () => {
       const border = 5;
       const wrapper = mount(<TextareaAutosize />);
-      assert.strictEqual(getHeight(wrapper), undefined);
+      assert.deepEqual(getStyle(wrapper), { height: undefined, overflow: null });
       setLayout(wrapper, {
         getComputedStyle: {
           'box-sizing': 'border-box',
@@ -127,7 +133,7 @@ describe('<TextareaAutosize />', () => {
       });
       wrapper.setProps();
       wrapper.update();
-      assert.strictEqual(getHeight(wrapper), 30 + border);
+      assert.deepEqual(getStyle(wrapper), { height: 30 + border, overflow: 'hidden' });
     });
 
     it('should take the padding into account with content-box', () => {
@@ -143,7 +149,7 @@ describe('<TextareaAutosize />', () => {
       });
       wrapper.setProps();
       wrapper.update();
-      assert.strictEqual(getHeight(wrapper), 30 - padding);
+      assert.deepEqual(getStyle(wrapper), { height: 30 - padding, overflow: 'hidden' });
     });
 
     it('should have at least height of "rows"', () => {
@@ -159,7 +165,7 @@ describe('<TextareaAutosize />', () => {
       });
       wrapper.setProps();
       wrapper.update();
-      assert.strictEqual(getHeight(wrapper), lineHeight * rows);
+      assert.deepEqual(getStyle(wrapper), { height: lineHeight * rows, overflow: null });
     });
 
     it('should have at max "rowsMax" rows', () => {
@@ -175,7 +181,7 @@ describe('<TextareaAutosize />', () => {
       });
       wrapper.setProps();
       wrapper.update();
-      assert.strictEqual(getHeight(wrapper), lineHeight * rowsMax);
+      assert.deepEqual(getStyle(wrapper), { height: lineHeight * rowsMax, overflow: null });
     });
 
     it('should update its height when the "rowsMax" prop changes', () => {
@@ -190,10 +196,10 @@ describe('<TextareaAutosize />', () => {
       });
       wrapper.setProps();
       wrapper.update();
-      assert.strictEqual(getHeight(wrapper), lineHeight * 3);
+      assert.deepEqual(getStyle(wrapper), { height: lineHeight * 3, overflow: null });
       wrapper.setProps({ rowsMax: 2 });
       wrapper.update();
-      assert.strictEqual(getHeight(wrapper), lineHeight * 2);
+      assert.deepEqual(getStyle(wrapper), { height: lineHeight * 2, overflow: null });
     });
   });
 });