TextInput's value property does not seem to get in sync when changing state via onChangeText.
An example use case would be blocking non numerical characters from the input.
This works on Android though.
OS: macOS High Sierra 10.13.3
Node: 8.10.0
Yarn: 1.5.1
npm: 5.6.0
Watchman: 4.9.0
Xcode: Xcode 9.3 Build version 9E145
Android Studio: 3.1 AI-173.4697961
react: ^16.3.0-alpha.1 => 16.3.0-rc.0
react-native: 0.54.3 => 0.54.3
TextInput value should be equal to state value even when it is modified during onChangeText.
TextInput value is not being modified.
Thanks for posting this! It looks like your issue may be missing some necessary information. Can you run react-native info
and edit your issue to include these results under the Environment section?
Thank you for your contributions.
I also have the same issue, any luck?
I am seeing a similar issue where I can't filter undesired characters from JS. I attempted to use the changes in the PR I tagged but I saw some issues:
The problem is that if you are using onChangeText to do text validation, it will briefly show the undesired character before removing it from the TextInput.
I don't know much about the React Native source code but after taking a brief look at the code, I think there might be a problem in textInputShouldChangeTextInRange in RCTBaseTextInputView.m. I think we should be preventing the text from being added until it comes in via the prop. Not entirely sure.
It seems on iOS, value property is not the one prioritized, instead it shows the actual value typed in. Isn't this supposed to be the same with both platforms?
OT: Why is this getting tag with "needs more information" while I already had it in the first place.
@shergin @PeteTheHeat @cbrevik Any thoughts? Looks like you are the three that contributed most to RCTBaseTextInputView.
I did some more digging and may have found a temporary fix for my project:
I noticed that onTextInput
is a prop that isn't documented anywhere but it's still fired. In RCTBaseTextInputView.m
, towards the end of - (BOOL)textInputShouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text
, it does an if check to see if this prop has been passed in and if so, fires the event and outside of the if statement, it returns YES
to allow the characters to be added.
For my fix, I just returned NO
inside of the if (_onTextInput)
check to prevent characters added in before I could do validation. I also pass _predictedText
for text
inside of the event instead of text. This allowed for me to do data validation in the JS layer and then set the new text via props.
I don't think I will be PRing this to React Native since it doesn't seem like a universal fix. Here is the diff if anyone is interested:
diff --git a/Libraries/Text/TextInput/RCTBaseTextInputView.m b/Libraries/Text/TextInput/RCTBaseTextInputView.m
index d9c47ff16..494d1715e 100644
--- a/Libraries/Text/TextInput/RCTBaseTextInputView.m
+++ b/Libraries/Text/TextInput/RCTBaseTextInputView.m
@@ -276,7 +276,7 @@ - (BOOL)textInputShouldChangeTextInRange:(NSRange)range replacementText:(NSStrin
if (_onTextInput) {
_onTextInput(@{
- @"text": text,
+ @"text": _predictedText,
@"previousText": previousText,
@"range": @{
@"start": @(range.location),
@@ -284,6 +284,8 @@ - (BOOL)textInputShouldChangeTextInRange:(NSRange)range replacementText:(NSStrin
},
@"eventCount": @(_nativeEventCount),
});
+
+ return NO;
}
return YES;
+1
Thanks, @rplankenhorn . It works for now. You should submit a PR...
@zaguini Glad it works for you! I am hesitant to submit a PR because it doesn't fix the underlying issue. onTextInput is undocumented and has been around since very very early on in the project.
I know TextInput was just re-written but I think we all need to think about what is the best solution. It could be something as simple as adding an additional prop to TextInput called requiresValidation
that forces textInputShouldChangeTextInRange
to return no and wait for the text to be updated via the text
prop. I am not sure.
I can try to come up with a better solution and submit a PR but I am swamped at work currently.
Yeah it should be ideal to mantain the React Native's TextInput
API... onChangeText
and onChange
instead of onTextInput
... A lot of compos rely on those previous cited methods.
Upon further testing, I found that modifying textInputShouldChangeTextInRange
with the above diff was the wrong approach. It caused two errors in my project:
The above fix is more robust as it allows for setting the text to an empty string if it's empty because it's been filtered.
+1
+1
Thanks for posting this! It looks like your issue may be missing some necessary information. Can you run react-native info
and edit your issue to include these results under the Environment section?
Thank you for your contributions.
Here's a demo about this problem. In the demo, only numbers are accepted in the input.
It works fine on Android. Not in iOS.
https://snack.expo.io/SJsmZ4DRM
Same issue. "Unable to prevent chars to be added on iOS. #19252"
@shergin This issue very serious, controlled mode of TextInput really broken. Can you please look at this? You the only one who create new Text implementation and probably the best person who can fix this properly in align with philosophy of new implementation!
+1
+1
Agreed, this is causing major issues in our app. I have been trying to hack around it, but it leads to a myriad of other issues.
+1
is there some temporary workaround for this issue?
@megabayt Yes, you can downgrade RN to older version (before Jan 2018). With current version you can't even use old Text component code, because it needs some support code from core that was removed.
is there any plan to fix this in the short term? I can not downgrade because I am using functionality of expo SDK 27 which depends on RN 0.55.X.
+1
If this is a regression, then it was probably caused by one of my changes to RCTBaseTextInputView in the last few months.
https://github.com/facebook/react-native/commit/38197c8230657d567170cdaf8ff4bbb4aee732b8#diff-a5239f085f0beab82ba2c1643be157ac
https://github.com/facebook/react-native/commit/6d9fe455dc815cdce86c00f81c71c9ca0c724964#diff-a5239f085f0beab82ba2c1643be157ac
https://github.com/facebook/react-native/commit/c136c54ff0211e2bf149fab600cd6e295f9d19dd#diff-a5239f085f0beab82ba2c1643be157ac
https://github.com/facebook/react-native/commit/ff70ecf868cf12fc66b45dc1496391d0a1e9011f#diff-a5239f085f0beab82ba2c1643be157ac
To hack around this you could try reverting these.
@ziqichen6 will be looking at this the week of 6/11. It'll be fixed within the next 2 weeks.
@PeteTheHeat At first look mentioned commits can't cause this bug, but I can test it this weekend. Only the last one touches attributedText
property...
My issue was solved when I replaced the extends 'Pure Component' by 'Component'.
I created repository react-native-textinput-bug to demonstrate the bug.
@PeteTheHeat I tested your suggestions, and reverting mentioned commits not fixed anything.
Details:
38197c8 Support Input Accessory View (iOS Only) [1/N] - reverted
6d9fe45 Fixed TVOS test failure caused by input accessory view change - reverted
c136c54 Refactor RCTInputAccessoryView view hierarchy and names - not reverted (code not in 0.55-stable branch)
ff70ecf Fixed
Sample code: https://github.com/vovkasm/react-native-textinput-bug/tree/try-backout-accessoryview
RN code: https://github.com/vovkasm/react-native/tree/without-accessoryview
+1
react-native 0.56.0-rc still buggy. https://github.com/vovkasm/react-native-textinput-bug/tree/rn-0.56
No Problem:
0.51.0 React 16.0.0 (Original for my project)
0.53.0 React 16.2.0
Cause Problem:
0.54.0 React 16.3.0-alpha.1(Context API), A lot of under-the-covers work on Yoga, iOS's Text and TextInput
0.55.0
0.55.2
0.55.3
0.55.4
Does anyone actively working to fix this? (I'm probably will have some time in near days and can try to deep dive into TextInput code)
I chipped at this problem for a week, and what's happening is that inside RCTBaseTextInputShadowView, there is a comparator for attributedText
and previousAttributedText
that sets the value of the boolean isAttributedTextChanged
The comparison right now only compares correct strings, so it doesn't update the text if an incorrect character is entered.
One fix I tried was to create a text setter in the file and add a flag that detects whether or not the text has been changed, and if so, also set isAttributedTextChanged
to true
However, javascript is a lot slower than native code so then the states get out of sync.
Cool! Then we need one of two things...
Note: yes it's very abstract thoughts currently... but may be will give you some more concrete insights. I still need to look at the code...
I attempted a fix in this PR but I haven't had time to fix the build issues. It works and I've manually applied the patch on my project.
@rplankenhorn Are you sure that you change doesn't broke autocomplete and hieroglyphic input methods? (I'm worry because this code compares actual text in textinput and potential text submitted from JS in UI thread but at that point of time, user can already type something that did not known to JS...)
I've been experiencing the same issue, here's a workaround I'm using:
validateUsername(username) {
// only alphanumerical and underscore
const filteredUsername = username.replace(/[^\w]/gi, '');
if(filteredUsername !== username) {
console.log('forcing update');
this.setState({ username: filteredUsername + '✕' }); // this character is not alphanumerical 'x', it's a forbidden character '✕' (cross)
setTimeout(() => {
this.setState((previousState) => {
return { ...previousState, username: previousState.username.replace(/[^\w]/gi, '') };
});
}, 0);
} else {
this.setState({ username: filteredUsername });
}
}
I set onChangeText={validateUsername}
. I've noticed that TextInput update doesn't work when new string's length is shorter than the one typed in by the user but it does work when the new string is the same length but different or when it is longer. When a forbidded character is detected, the code above briefly adds and then removes another forbidden character. This forces TextInput to update where setState alone wouldn't. I'm using a state update function to avoid the state getting out of sync when the user is typing fast.
This is crazy.
This issue still alive? Hmm.
@zaguiini Yes
any solution ?
IIUC, as of 6 months ago the feature of controlled text inputs is effectively no longer supported by React Native.
Does anyone have any insights into how apps have been able to use React Native despite this issue, s.t. it wasn't pressing enough for RN to fix it in 0.55/0.56?
I would imagine many apps need to have an input whose content is restricted by some app logic. So, is it that there's an alternative way of achieving this same user-facing effect, without needing onChangeText? (E.g. something like using a custom component instead of TextInput?)
Or, is it that most apps still use React Native <=0.53 nowadays?
First of all, I thought it's odd that we put the state into value
of TextInput and at the same time, execute setState
from onChangedText
. It's like the endless call because setState
trigger re-render and it triggers onChangedText
.
But it actually does not happen. But it's still odd to me.
I didn't look the code in how they managed the bridge though.
@benevbright I'm a bit confused about what you're trying to say or whether it's relevant to this thread at all. If you're simply talking about how controlled-component works, I don't think there is an "endless call" because both setState
and re-rendering don't trigger onChangeText
. Only a native event will trigger the callback.
Opps. @ycai2 You are right.
I tested it and yes, setState doesn't trigger onChangeText
.
Sorry about wrong information.
Anyway, for workaround.
Option 1. Don't execute setState
on onChangeText
. Instead, just keep changed TextValue into variable.
Option 2. If you need to execute setState
on onChangeText
, don't put the relative state(changed from setState on onChangeText) into value prop
of TextInput, it will re-render TextInput and it will be broken. Instead, set value
or defaultValue
with using class' variable like this.textValue
.
@Rovack do you have a source for the claim that controlled inputs are no longer supported? This would be something major since it strongly deviates from the path that React takes, or am I wrong?
@peterjuras Obviously. This and some other bugs is source to claim that "as of 6 months ago the feature of controlled text inputs is effectively no longer supported by React Native". All these bugs are the consequences of rewriting text subsystem near the end of January 2018.
Without the phrase "effectively" I'm sure, facebook will claim that controlled inputs are supported. But if you try to use them in most practical cases they are not working. And the fact that such bugs are not fixed for so long is a strong message for community.
It makes so hard to do some kind of text pattern matching/filtering in inputs with this bug...
We really need a fix asap...
@peterjuras Yes, it does seem like something major, which strongly deviates from React.
That's why it's not clear to me how this could have remained open for this long, and leads me to wonder if I'm missing something.
As @vovkasm points out, this entire issue is the source for this claim.
I suppose inputs do still let a parent access the value of a TextInput, but not change it, and without that functionality it's hard to call an input "controlled".
We also have this issue on 0.56 . Thanks @benevbright for the workaround.
EDIT: workaround did not help for me.
Could somebody share an example of the workaround? @ou2s or @benevbright maybe?
@vdlindenmark the workaround did not work for us. We ended up not using a controlled component on iOS. IMHO, this is a serious bug. Can someone tag people from RN team to check if they are aware of it?
@ou2s They certainly know about this: https://github.com/facebook/react-native/issues/18874#issuecomment-382751382
@benevbright the workarounds didn't work for me. It might be due to the length of the string I'm formatting to... I don't think there is anything we can do on Javascript side to workaround this issue. Although, ideally, RN can just fix it in a upcoming version.
@hramos just checking: is this problem serious enough to be prioritized and fixed any time soon?
@ycai2 @vdlindenmark
Hi, guys.
I have to say that I'm sorry about it.
I misunderstood the original problem.
After I noticed, I tried to find a workaround with above example for hours.
Nothing is working.
I thought it's the same problem with Korean character problem. (it's another issue)
I marked my previous comment as the wrong comment.
@benevbright Oh I guess that makes sense. I also thought it was related. I saw a bunch issues that are similar, and some of them were referenced in this thread, so I thought it's the same problem. Thanks for the help though!
Just checking, does https://github.com/facebook/react-native/commit/892212bad2daadd373f4be241e4cd9889b0a1005 fix the issue described in this thread?
@hramos Tested applying these changes on 55.3 and did not fix this issue.
@yairopro Please see my PR
#20634 , I tested and it works.
@zhongwuzw I think you picked the wrong guy. I am not able to merge your PR .
@yairopro 😂 I mean my PR
may solved the demo you provided.
@zhongwuzw ok. Good word dude 👍. Now let's see how this issue will be resolved with your PR😄
As of RN56:
@rplankenhorn solution breaks onChange/onChangeText, but allow full controlled input (using onKeyPress
)
I started of @rplankenhorn solution, and then forwarded a "controlled" flag to RCTBaseTextInputView
which only block textInputShouldChangeTextInRange
on controlled input.
I then created a <ControlledTextInput/>
module which recreate onChangeText
behavior using onKeyPress
and onSelectionChange
You can find the module with doc and example here 😀
https://github.com/abelcha/react-native-controlled-input
https://www.npmjs.com/package/react-native-controlled-input
@shergin @PeteTheHeat @cbrevik Still no response? In almost 5 months having this major bug open... Everyone in the community experimenting workarounds and messing their own projects doing regressions because the version released is simply not doing its job correctly.
Its normal to have bugs when features or code restructuring iterations are made, but this 5 month bug resolving threshold, projects absent commitment.
Exactly as @vovkasm said, this is indeed a very strong message for community...
if the textInput text length is longer than state text length,the text can't be change ,I don't want to change objective-c code,so I try a hack code,like this.
onChangeText={(text)=>{
console.log('输入的text',text);
if ( parseInt(text)> 999){
setTimeout(()=>{this.ref.clear()},10);
setTimeout(()=>{this.setState({phoneNo:'999'})},50);
}else{
this.setState({phoneNo:text});
}
}}
the core method is clear method, if I clear textInput,the textInput text length is shorter than state text length ,it is workaround for me.
@abelcha thank you for the module, but if this is the way everyone will have to end up using react-native, it would be ridiculous...
It would seem like the source for this bug is in RCTBaseTextInputView.m, if anyone wants to take a stab at fixing this.
Frankly, this is not an issue that has shown up in our own apps, therefore it hasn't become a priority for us to dedicate time to fixing it. Please do send a PR if you'd like to propose a fix.
We're also happy with taking @abelcha's approach of using third party modules; in fact, we're working towards a slimmer React Native core, where components such as Text Inputs might all be imported from npm packages outside of the RN core.
I've been experiencing the same issue, here's a workaround I'm using:
validateUsername(username) { // only alphanumerical and underscore const filteredUsername = username.replace(/[^\w]/gi, ''); if(filteredUsername !== username) { console.log('forcing update'); this.setState({ username: filteredUsername + '✕' }); // this character is not alphanumerical 'x', it's a forbidden character '✕' (cross) setTimeout(() => { this.setState((previousState) => { return { ...previousState, username: previousState.username.replace(/[^\w]/gi, '') }; }); }, 0); } else { this.setState({ username: filteredUsername }); } }
I set
onChangeText={validateUsername}
. I've noticed that TextInput update doesn't work when new string's length is shorter than the one typed in by the user but it does work when the new string is the same length but different or when it is longer. When a forbidded character is detected, the code above briefly adds and then removes another forbidden character. This forces TextInput to update where setState alone wouldn't. I'm using a state update function to avoid the state getting out of sync when the user is typing fast.
works for me
I'm concatenating alternated invisible characters to workaround this bug.
Ugly hack:
const inputWorkaround = (() => {
let workaroundIncrement = 0
const invisibleCharsArr = [
String.fromCharCode(28),
String.fromCharCode(29),
String.fromCharCode(30),
String.fromCharCode(31),
]
return {
getWorkaroundChar: () => {
workaroundIncrement += 1
const mod = workaroundIncrement % invisibleCharsArr.length
return invisibleCharsArr[mod]
}
}
})()
class MyComponent extends React.Component {
render() {
const prefixedValue = inputWorkaround.getWorkaroundChar() + this.props.value
return (<TextInput value={prefixedValue} />)
}
}
You'll probably want to avoid doing this on Android. Some OS versions will print the invisible chars.
I'm concatenating alternated invisible characters to workaround this bug.
Ugly hack:const inputWorkaround = (() => { let workaroundIncrement = 0 const invisibleCharsArr = [ String.fromCharCode(28), String.fromCharCode(29), String.fromCharCode(30), String.fromCharCode(31), ] return { getWorkaroundChar: () => { workaroundIncrement += 1 const mod = workaroundIncrement % invisibleCharsArr.length return invisibleCharsArr[mod] } } })() class MyComponent extends React.Component { render() { const prefixedValue = inputWorkaround.getWorkaroundChar() + this.props.value return (<TextInput value={prefixedValue} />) } }
You'll probably want to avoid doing this on Android. Some OS versions will print the invisible chars.
Holy crap.
Anyways, bug's fixed by the RN Team.
Anyways, bug's fixed by the RN Team.
Fixed in which version @zaguiini ?
Latest (v0.57.0), @fernandofranca. Check the CHANGELOG.
I can confirm, that this bug fixed in RN 0.57, tested with this simple application (branch rn-0.57): https://github.com/vovkasm/react-native-textinput-bug/tree/rn-0.57
Also some flicker exists, it's currently unavoidable due to inter threads communication with controlled text inputs (information should move between UI and JS threads).
I too have the issue with 0.57
@paramadeep Can you create reproducible example as in https://github.com/vovkasm/react-native-textinput-bug/ ?
Can someone point me to the commit that fixed it?
@danReynolds, I think it is https://github.com/facebook/react-native/commit/2307ea60d03edd234511bfe32474c453f30c1693
This is in 0.57.1.
Most helpful comment
@shergin This issue very serious, controlled mode of TextInput really broken. Can you please look at this? You the only one who create new Text implementation and probably the best person who can fix this properly in align with philosophy of new implementation!