Skip to content

Commit

Permalink
[TextareaAutosize] Fix warnings for too many renders in React 18 (#33253
Browse files Browse the repository at this point in the history
)
  • Loading branch information
mnajdova committed Jun 23, 2022
1 parent b90770d commit fd279d6
Showing 1 changed file with 66 additions and 30 deletions.
96 changes: 66 additions & 30 deletions packages/mui-base/src/TextareaAutosize/TextareaAutosize.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import * as React from 'react';
import PropTypes from 'prop-types';
import { flushSync } from 'react-dom';
import {
unstable_debounce as debounce,
unstable_useForkRef as useForkRef,
Expand Down Expand Up @@ -27,6 +28,10 @@ const styles = {
},
};

function isEmpty(obj) {
return obj === undefined || obj === null || Object.keys(obj).length === 0;
}

const TextareaAutosize = React.forwardRef(function TextareaAutosize(props, ref) {
const { onChange, maxRows, minRows = 1, style, value, ...other } = props;

Expand All @@ -37,14 +42,14 @@ const TextareaAutosize = React.forwardRef(function TextareaAutosize(props, ref)
const renders = React.useRef(0);
const [state, setState] = React.useState({});

const syncHeight = React.useCallback(() => {
const getUpdatedState = React.useCallback(() => {
const input = inputRef.current;
const containerWindow = ownerWindow(input);
const computedStyle = containerWindow.getComputedStyle(input);

// If input's width is shrunk and it's not visible, don't sync height.
if (computedStyle.width === '0px') {
return;
return {};
}

const inputShallow = shadowRef.current;
Expand Down Expand Up @@ -86,41 +91,72 @@ const TextareaAutosize = React.forwardRef(function TextareaAutosize(props, ref)
const outerHeightStyle = outerHeight + (boxSizing === 'border-box' ? padding + border : 0);
const overflow = Math.abs(outerHeight - innerHeight) <= 1;

setState((prevState) => {
// Need a large enough difference to update the height.
// This prevents infinite rendering loop.
if (
renders.current < 20 &&
((outerHeightStyle > 0 &&
Math.abs((prevState.outerHeightStyle || 0) - outerHeightStyle) > 1) ||
prevState.overflow !== overflow)
) {
renders.current += 1;
return {
overflow,
outerHeightStyle,
};
}
return { outerHeightStyle, overflow };
}, [maxRows, minRows, props.placeholder]);

if (process.env.NODE_ENV !== 'production') {
if (renders.current === 20) {
console.error(
[
'MUI: Too many re-renders. The layout is unstable.',
'TextareaAutosize limits the number of renders to prevent an infinite loop.',
].join('\n'),
);
}
const updateState = (prevState, newState) => {
const { outerHeightStyle, overflow } = newState;
// Need a large enough difference to update the height.
// This prevents infinite rendering loop.
if (
renders.current < 20 &&
((outerHeightStyle > 0 &&
Math.abs((prevState.outerHeightStyle || 0) - outerHeightStyle) > 1) ||
prevState.overflow !== overflow)
) {
renders.current += 1;
return {
overflow,
outerHeightStyle,
};
}
if (process.env.NODE_ENV !== 'production') {
if (renders.current === 20) {
console.error(
[
'MUI: Too many re-renders. The layout is unstable.',
'TextareaAutosize limits the number of renders to prevent an infinite loop.',
].join('\n'),
);
}
}
return prevState;
};

const syncHeight = React.useCallback(() => {
const newState = getUpdatedState();

if (isEmpty(newState)) {
return;
}

return prevState;
setState((prevState) => {
return updateState(prevState, newState);
});
}, [maxRows, minRows, props.placeholder]);
}, [getUpdatedState]);

const syncHeightWithFlushSycn = () => {
const newState = getUpdatedState();

if (isEmpty(newState)) {
return;
}

// In React 18, state updates in a ResizeObserver's callback are happening after the paint which causes flickering
// when doing some visual updates in it. Using flushSync ensures that the dom will be painted after the states updates happen
// Related issue - https://github.com/facebook/react/issues/24331
// TODO: Do this only in the resize observer?
flushSync(() => {
setState((prevState) => {
return updateState(prevState, newState);
});
});
};

React.useEffect(() => {
const handleResize = debounce(() => {
renders.current = 0;
syncHeight();
syncHeightWithFlushSycn();
});
const containerWindow = ownerWindow(inputRef.current);
containerWindow.addEventListener('resize', handleResize);
Expand All @@ -138,7 +174,7 @@ const TextareaAutosize = React.forwardRef(function TextareaAutosize(props, ref)
resizeObserver.disconnect();
}
};
}, [syncHeight]);
});

useEnhancedEffect(() => {
syncHeight();
Expand Down

0 comments on commit fd279d6

Please sign in to comment.