From fc8bf7aa210913c67bc9fae26d22295f2c24b732 Mon Sep 17 00:00:00 2001 From: ZeeshanTamboli Date: Fri, 26 Jan 2024 14:47:50 +0530 Subject: [PATCH 1/8] initial refactoring --- .../TextareaAutosize.test.tsx | 28 ------- .../src/TextareaAutosize/TextareaAutosize.tsx | 74 ++++--------------- 2 files changed, 15 insertions(+), 87 deletions(-) diff --git a/packages/mui-base/src/TextareaAutosize/TextareaAutosize.test.tsx b/packages/mui-base/src/TextareaAutosize/TextareaAutosize.test.tsx index 76db8c8d003cd9..fe7d02eccb19e6 100644 --- a/packages/mui-base/src/TextareaAutosize/TextareaAutosize.test.tsx +++ b/packages/mui-base/src/TextareaAutosize/TextareaAutosize.test.tsx @@ -9,7 +9,6 @@ import { createMount, createRenderer, fireEvent, - strictModeDoubleLoggingSuppressed, } from '@mui-internal/test-utils'; import { TextareaAutosize } from '@mui/base/TextareaAutosize'; @@ -458,32 +457,5 @@ describe('', () => { // the input should be 2 lines expect(input.style).to.have.property('height', `${lineHeight * 2}px`); }); - - describe('warnings', () => { - it('warns if layout is unstable but not crash', () => { - const { container, forceUpdate } = render(); - const input = container.querySelector('textarea[aria-hidden=null]')!; - const shadow = container.querySelector('textarea[aria-hidden=true]')!; - let index = 0; - setLayout(input, shadow, { - getComputedStyle: { - boxSizing: 'content-box', - }, - scrollHeight: 100, - lineHeight: () => { - index += 1; - return index; - }, - }); - - expect(() => { - forceUpdate(); - }).toErrorDev([ - 'MUI: Too many re-renders.', - !strictModeDoubleLoggingSuppressed && 'MUI: Too many re-renders.', - !strictModeDoubleLoggingSuppressed && 'MUI: Too many re-renders.', - ]); - }); - }); }); }); diff --git a/packages/mui-base/src/TextareaAutosize/TextareaAutosize.tsx b/packages/mui-base/src/TextareaAutosize/TextareaAutosize.tsx index 9ee44c2fb2d923..852f54e22a74a1 100644 --- a/packages/mui-base/src/TextareaAutosize/TextareaAutosize.tsx +++ b/packages/mui-base/src/TextareaAutosize/TextareaAutosize.tsx @@ -1,7 +1,6 @@ 'use client'; import * as React from 'react'; import PropTypes from 'prop-types'; -import * as ReactDOM from 'react-dom'; import { unstable_debounce as debounce, unstable_useForkRef as useForkRef, @@ -68,9 +67,8 @@ const TextareaAutosize = React.forwardRef(function TextareaAutosize( const handleRef = useForkRef(forwardedRef, inputRef); const shadowRef = React.useRef(null); const renders = React.useRef(0); - const [state, setState] = React.useState({ - outerHeightStyle: 0, - }); + const heightRef = React.useRef(0); + const overflowRef = React.useRef(false); const getUpdatedState = React.useCallback(() => { const input = inputRef.current!; @@ -127,35 +125,6 @@ const TextareaAutosize = React.forwardRef(function TextareaAutosize( return { outerHeightStyle, overflow }; }, [maxRows, minRows, props.placeholder]); - const updateState = (prevState: State, newState: State) => { - 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(); @@ -163,30 +132,24 @@ const TextareaAutosize = React.forwardRef(function TextareaAutosize( return; } - setState((prevState) => updateState(prevState, newState)); - }, [getUpdatedState]); + const input = inputRef.current!; - useEnhancedEffect(() => { - const syncHeightWithFlushSync = () => { - const newState = getUpdatedState(); + if (heightRef.current !== newState.outerHeightStyle) { + heightRef.current = newState.outerHeightStyle; + } - if (isEmpty(newState)) { - return; - } + if (overflowRef.current !== newState.overflow) { + overflowRef.current = newState.overflow!; + } - // In React 18, state updates in a ResizeObserver's callback are happening after - // the paint, this leads to an infinite rendering. - // - // Using flushSync ensures that the states is updated before the next pain. - // Related issue - https://github.com/facebook/react/issues/24331 - ReactDOM.flushSync(() => { - setState((prevState) => updateState(prevState, newState)); - }); - }; + input.style.setProperty('height', `${newState.outerHeightStyle}px`); + input.style.setProperty('overflow', newState.overflow ? 'hidden' : ''); + }, [getUpdatedState]); + useEnhancedEffect(() => { const handleResize = () => { renders.current = 0; - syncHeightWithFlushSync(); + syncHeight(); }; // Workaround a "ResizeObserver loop completed with undelivered notifications" error // in test. @@ -222,7 +185,7 @@ const TextareaAutosize = React.forwardRef(function TextareaAutosize( resizeObserver.disconnect(); } }; - }, [getUpdatedState]); + }, [getUpdatedState, syncHeight]); useEnhancedEffect(() => { syncHeight(); @@ -252,13 +215,6 @@ const TextareaAutosize = React.forwardRef(function TextareaAutosize( ref={handleRef} // Apply the rows prop to get a "correct" first SSR paint rows={minRows as number} - style={{ - height: state.outerHeightStyle, - // Need a large enough difference to allow scrolling. - // This prevents infinite rendering loop. - overflow: state.overflow ? 'hidden' : undefined, - ...style, - }} {...other} />