Skip to content

[fix] TextInput onContentSizeChange reports a shrinking content size - #2860

Open
u9g wants to merge 1 commit into
necolas:masterfrom
u9g:textinput-content-size-shrink
Open

u9g wants to merge 1 commit into
necolas:masterfrom
u9g:textinput-content-size-shrink

Conversation

@u9g

@u9g u9g commented Sep 25, 2026

Copy link
Copy Markdown

onContentSizeChange on a multiline TextInput reads the textarea's own scrollHeight, which is never less than the height the textarea currently has. Once an auto-growing input has grown (the app sets height from the reported size), deleting lines keeps reporting the old, larger size, so the input can never shrink back. This is #2160 (and the earlier #1339/#1399).

The fix measures a zero-height, hidden copy of the textarea instead of the textarea itself. The copy is a cloneNode() (so it keeps the same classes and inline styles, and inherits from the same parent), gets the textarea's computed width so wrapping matches, and is position: fixed / visibility: hidden / overflow: hidden so it neither moves anything nor grows a scrollbar that would change wrapping. It is inserted and removed synchronously, so nothing is painted. This is the same technique react-textarea-autosize uses. Temporarily setting the real textarea's height to 0 would be simpler, but the forced layout clamps the scroll position of any container the textarea sits at the bottom of (a chat composer being the typical case), and restoring the height does not restore the scroll offset.

Reported size is still scrollHeight/scrollWidth of a textarea (content plus padding), so values for content that fits are unchanged; only content smaller than the box now reports its real height.

Tested with a jest test that mocks scrollHeight (jsdom has no layout) and checks the reported height goes 10 → 30 → 10 as lines are added and removed, that the real textarea's height is untouched and that no clone is left in the DOM. Also checked in Chrome on a page scrolled to the bottom: a 100px-high textarea with one line reports 40px (line + padding) instead of 98px, eight lines report 180px as before, long lines wrap to the same height as in the textarea, and window.scrollY is unchanged after measuring. npm run unit, lint and format pass for the package; flow could not be run locally (flow-bin has no arm64 build), so relying on CI for it.

The content size was read from the textarea's own scrollHeight, which is
never less than the height the textarea has been given. So once an
auto-growing input has grown, deleting lines never reports a smaller
size and the input can't shrink back. Measure a zero-height, hidden copy
of the textarea instead, which reports the height of the content alone
and leaves the textarea's box and the page's scroll position untouched.

Close necolas#2160
@codesandbox-ci

Copy link
Copy Markdown

This pull request is automatically built and testable in CodeSandbox.

To see build info of the built libraries, click here or the icon next to each commit SHA.

Latest deployment of this branch, based on commit edde968:

Sandbox Source
react-native-web-examples Configuration

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant