(RN 79.5) TextInput onContentSizeChange is not triggered on iOS
Description
I noticed that after updating to 0.79.5, TextInput.onContentSizeChange is no longer triggered even if the multiline is set to true and input does growth properly. It is only triggered once, when the component mounts.
Steps to reproduce
- Add any TextInput component with onContentSizeChange prop, eg.
onContentSizeChange={() => Alert.alert('hello')} - Render the app with iOS-simulator
- Press enter on input, nothing happens.
React Native Version
0.79.5
Affected Platforms
Runtime - iOS
Output of npx @react-native-community/cli info
info Fetching system and libraries information...
System:
OS: macOS 14.6.1
CPU: (8) arm64 Apple M1
Memory: 150.19 MB / 16.00 GB
Shell:
version: "5.9"
path: /bin/zsh
Binaries:
Node:
version: 20.18.1
path: /usr/local/bin/node
Yarn:
version: 1.22.22
path: ~/.npm-global/bin/yarn
npm:
version: 10.8.2
path: /usr/local/bin/npm
Watchman:
version: 2025.04.14.00
path: /opt/homebrew/bin/watchman
Managers:
CocoaPods:
version: 1.16.2
path: /opt/homebrew/lib/ruby/gems/3.0.0/bin/pod
SDKs:
iOS SDK:
Platforms:
- DriverKit 24.2
- iOS 18.2
- macOS 15.2
- tvOS 18.2
- visionOS 2.2
- watchOS 11.2
Android SDK: Not Found
IDEs:
Android Studio: 2024.1 AI-241.18034.62.2411.12071903
Xcode:
version: 16.2/16C5032a
path: /usr/bin/xcodebuild
Languages:
Java:
version: 22.0.1
path: /usr/bin/javac
Ruby:
version: 3.4.2
path: /opt/homebrew/opt/ruby/bin/ruby
npmPackages:
"@react-native-community/cli": Not Found
react:
installed: 19.0.0
wanted: 19.0.0
react-native:
installed: 0.79.5
wanted: 0.79.5
react-native-macos: Not Found
npmGlobalPackages:
"*react-native*": Not Found
Android:
hermesEnabled: Not found
newArchEnabled: Not found
iOS:
hermesEnabled: Not found
newArchEnabled: Not found
info React Native v0.80.2 is now available (your project is running on v0.79.5).
info Changelog: https://github.com/facebook/react-native/releases/tag/v0.80.2
info Diff: https://react-native-community.github.io/upgrade-helper/?from=0.79.5&to=0.80.2
info For more info, check out "https://reactnative.dev/docs/upgrading?os=macos".
Stacktrace or Logs
None
MANDATORY Reproducer
https://snack.expo.dev/@njaah/textinput-issue?platform=ios
Screenshots and Videos
No response
Thanks for the bug, I was able to reproduce this on RN Tester. Was this working on RN 0.78?
@riteshshukla04 I also started experiencing this problem after updating expo to v53. I don't know if it was working on 0.78 but it was definitely working on 0.76.9 which was the version I was using before the expo update.
Thanks for the bug, I was able to reproduce this on RN Tester. Was this working on RN 0.78?
I am not sure about 0.78, my previous version was Expo 50 and I can't remember which RN version that uses. But it was definitely working back then.
Also, in case someone is struggling with this issue, you can quite easily overcome it with the help of the onLayout prop, which is still triggered correctly.
react-native-gifted-chat is affected by this issue, as it doesn't report composer height correctly.
@Njaah-0 Thanks 🙏
it reproducible on latest react-native version 0.81.0
I believe this will be helpful to you: https://github.com/facebook/react-native/pull/50782
I am also experiencing this issue with 0.81.1. @Njaah-0 can you elaborate on how onLayout fixes this? On my end it looks like that is also not firing on ios.
@cmhac
You can render Text with the same fontSize & lineHeight & width and use onLayout to set the height of the input
@uen an example could be better rather than just words.
I also had issues using onLayout.
@uen's solution worked well. Hopefully I can delete this code soon, yikes. 😅
const [text, setText] = useState<string>('');
const [inputHeight, setInputHeight] = useState(100); // initial height
// The Text component is used to measure the height of the text input content, as onContentSizeChange is not triggering correctly on ios.
// https://github.com/facebook/react-native/issues/52854
return (
<KeyboardAwareScrollView className="p-3">
<Text
onLayout={(event) => {
setInputHeight(event.nativeEvent.layout.height);
}}
className="opacity-0 absolute"
>
{text}
</Text>
<TextInput
textAlignVertical="top" // keeps the text anchored at the top
multiline={true}
onChangeText={setText}
className="text-md"
style={{ color: colors.text, height: Math.max(100, inputHeight) + 30 }}
underlineColorAndroid="transparent"
autoFocus={true}
scrollEnabled={false}
/>
</KeyboardAwareScrollView>
);
Hello, same bug here. The multiline TextInput does not grow automatically anymore, and onContentSizeChange is not called either. I just upgraded to the latest Expo recently. It was working on previous React-Native versions!
Same issue here, using Expo 54.0.22 with react-native: 0.81.5
In my case, to fix this issue I had to execute this once:
textInputRef.current?.setNativeProps({ text });
More details : https://github.com/facebook/react-native/issues/52854#issuecomment-3514945557
In my case, to fix this issue I had to execute this once:
textInputRef.current?.setNativeProps({ text });
Lovely, I was impacted by both bugs:
- onContentSizeChange is never triggered except on mount
- TextInput with multiline doesn't grow automatically anymore on new RN version.
By doing @BouarourMohammed it fixes the bugs
In my case, to fix this issue I had to execute this once:
textInputRef.current?.setNativeProps({ text });Lovely, I was impacted by both bugs:
- onContentSizeChange is never triggered except on mount
- TextInput with multiline doesn't grow automatically anymore on new RN version.
By doing @BouarourMohammed it fixes the bug
Did it solved both bugs? Because I'm trying to fix the TextInput multiline autogrow by running setNativeProps as @BouarourMohammed said when the TextInput onLayout event is fired and it's not working:
https://snack.expo.dev/@patosala/multiline-textinput-inside-scrollview
@PatoSala What I did to fix it was using a useState on iOS, far from ideal as it forces re-rendering the input but at least it auto grows...
// MultilineInput.ios.tsx
export function MultilineInput({
value,
onChangeText,
...restProps
}: TextInputProps) {
const [tmpValue, setTmpValue] = useState(value);
function handleTextChange(value: string) {
if (onChangeText) {
onChangeText(value);
}
setTmpValue(value);
}
return (
<TextInput
multiline
value={tmpValue}
onChangeText={handleTextChange}
{...restProps}
/>
);
}
@PatoSala To make this work, the text value needs at least one character when using setNativeProps. Update your code accordingly and it should work.
const handleOnLayout = () => {
inputRef.current.setNativeProps({
text: "A"
});
}
Or you can initialize it at mount—it's up to you how you want to tweak it—just make sure it only runs once at the start: For example: https://snack.expo.dev/@bouarourmohammed/multiline-textinput-inside-scrollview
import { StyleSheet, View, TextInput } from 'react-native';
import { useRef } from "react";
export default function App() {
const inputRef = useRef(null);
const initialised = useRef(false)
return (
<View style={{
paddingTop: 100,
flex: 1
}}>
<TextInput
ref={inputRef}
onChangeText={(text)=> {
if(!initialised.current && text?.trim()){
initialised.current = true
inputRef.current?.setNativeProps({text})
}
}}
multiline={true}
scrollEnabled={false}
onContentSizeChange={(e) => console.log(e)}
/>
</View>
);
}
@BouarourMohammed you are right, it needs to at least have one chacracter. I really need the TextInput to be empty so I tried setting text to some value and then set it again but this time empty, like this:
const handleOnLayout = () => {
inputRef.current.setNativeProps({
text: " "
});
inputRef.current.setNativeProps({
text: " "
});
}
But it doesn't work. I guess we'll have to wait till someone fixes this.
Considering my whole app has a single font size for my inputs, I was able to come up with this "hack".
- I used the onLayout to check the width of my input and I counted how many characters I was able to type within that width (39 chars). Example: my input had 392 of width, my input has 16 of padding on left and right (32 in total) and I could type 39 chars, so:
392 (input) - 32 (horizontal paddings)
/ 39 characteres
=
each character takes ~9.23 of width
-
Added state for input width const [inputHeight, setInputHeight] = useState(DEFAULT_HEIGHT); const [inputWidth, setInputWidth] = useState(0);
-
Implement height calculation based on text content
useEffect(() => {
if (!multiline || !inputWidth || !value) {
if (multiline && minHeight) {
setInputHeight(minHeight);
}
return;
}
// Subtract horizontal padding (e.g., 16px * 2 = 32px)
const paddingHorizontal = SPACING.md * 2;
const availableWidth = inputWidth - paddingHorizontal;
// Calibrate: test how many characters fit per line
// Example: 360px = 39 chars → ~9.23px per character
const charWidth = 9.23; // ADJUST THIS VALUE FOR YOUR FONT
const charsPerLine = Math.floor(availableWidth / charWidth);
// Count lines (including explicit \n breaks)
const lines = value.split('\n');
let totalLines = 0;
lines.forEach(line => {
if (line.length === 0) {
totalLines += 1;
} else {
totalLines += Math.ceil(line.length / charsPerLine);
}
});
// Calculate height (adjust lineHeight for your font)
const lineHeight = 24; // font-size * 1.5
const paddingVertical = SPACING.md + SPACING.sm;
let calculatedHeight = (totalLines * lineHeight) + paddingVertical;
// Apply constraints
if (minHeight && calculatedHeight < minHeight) {
calculatedHeight = minHeight;
}
if (maxHeight && calculatedHeight > maxHeight) {
calculatedHeight = maxHeight;
}
setInputHeight(Math.ceil(calculatedHeight));
}, [value, inputWidth, multiline, minHeight, maxHeight]);
- Capture input width
<TextInput
onLayout={(e) => {
setInputWidth(e.nativeEvent.layout.width);
}}
// ...
/>
- Apply calculated height and disable scroll
<TextInput
multiline={multiline}
style={[
multiline ? {
height: inputHeight,
minHeight: minHeight || DEFAULT_HEIGHT,
maxHeight: maxHeight,
} : { height: DEFAULT_HEIGHT }
]}
/>
If the iPhone has some accessibility tool turned on, like increased font size for the OS, this calculation may need to be adjusted. Something worth considering is using
import { PixelRatio } from "react-native";
(...)
const charWidth = 9.23 * PixelRatio.getFontScale();
In case you're struggling with this issue, multiline is only working on controlled inputs.
In case you're struggling with this issue, multiline is only working on controlled inputs.
Thank you! I've been trying a bunch of different things and it ended up being this for me. Uncontrolled inputs seem to return the same contentSize values even after updating the value.
Most of the suggestions above didn't work in my case. What helped for me was removing the height Attribute and also on the container around it and suddently it grew again with the text. So I changed it to only have the height attribute when text was getting too big.
const [text, setText] = useState<string>('')
const [textHeight, setTextHeight] = useState<number>(32)
const contentSizeChange = (event: TextInputContentSizeChangeEvent) => {
setTextHeight(event.nativeEvent.contentSize.height)
}
...
<View
style={[styles.container, textHeight >= 150 && { height: 150 }]}
>
<TextInput
value={text}
style={[styles.input]}
multiline={true}
scrollEnabled={true}
onContentSizeChange={contentSizeChange}
onChangeText={(newText) => {
setText(newText)
}}
/>
</View>
In this case, when height is 150, the style is applied and again, onContentSizeChange will not trigger anymore, when text is getting bigger. But it will trigger when text is getting smaller, box will become smaller, height style is removed and trigger works in both directions again.
As soon as there was something controlling it's height (even setting minHeight) the onContentSizeChange didn't trigger anymore.