I am fairly new to React Native and I have problem with using onChangeText.
I am trying to have a TextInput to type in a word and updating a state. When I use onChangeText I can only type 1 symbol at a time until it re-renders. I can keep the value by using
value = {this.state.text} but the input field still lose focus everytime I write a letter.
I have also tried using onBlur and onSubmitEditing with no success.
This is my current code. Which is inside render().
<View style={{padding: 10}}>
<TextInput
onChangeText={(text) => { this.setState({text: text})} }
/>
<TouchableHighlight style={styles.button} onPress={this.handlePress.bind(this)}>
<Text style={styles.buttonText}>Login</Text>
</TouchableHighlight>
<Text style={{padding: 10, fontSize: 42}}>
{this.state.text}
</Text>
</View>
So by using this method I can currently only write one letter at a time as this.state.text will only consist of one letter at a time.
Any help appreciated.
Example
SOLVED
I used react-native-tab-view which uses it's own router.
I wrote my code as this
And as you see the rendering part happens outside of return(). That's what caused my problem.
I've removed react-native-tab-view and rewritten it like this
<TextInput style={styles.input}
placeholder='username'
onChangeText={(text) => { this.setState({ username: text})}}>
</TextInput>
You need { } to open and close the function block, else it return the setState
() => callFn is equivalent with () => {return callFn} so you return your setState call.
You need here () => {callFn}
And remove the {this.state.text} from your <Text> tag, that will trigger rerender every time you change the state
Try with this full component hope so this helpfull for u.
'use strict';
import React, { Component } from "react";
import { Text, View, TextInput } from 'react-native';
class Home extends Component {
constructor(props) {
super(props);
this.state = {
text:''
};
}
render() {
let {text}=this.state;
return (
<View style={{padding: 10}}>
<TextInput onChangeText={(text) => { this.setState({ text: text})}}/>
<Text style={{padding: 10, fontSize: 42}}>
{text}
</Text>
</View>
)
}
}
export default Home;
It is not best practice to create functions within component props. This will always force a re-render even if nothing was changed due to the fact that the prop value is a new function.
Try it like this.
I also gave you a way to have multiple text inputs without creating a single inline function by use of "currying", along with making them into controlled inputs whereby their value is "controlled" by the state. Socialism in React!
'use strict';
import React, { Component } from "react";
import { Text, View, TextInput, StyleSheet } from 'react-native';
class Home extends Component {
constructor(props) {
super(props);
this.state = {
name:''
email:''
nameError:''
emailError:''
};
}
onChangeText = name => text => this.setState({ [name]: text });
render() {
let { name, email, nameError, emailError } = this.state;
return (
<View style={styles.container}>
<TextInput onChangeText={this._onChangeText("name")} value={name} />
<Text style={styles.text}>{nameError}</Text>
<TextInput onChangeText={this._onChangeText("email"} value={email} />
<Text style={styles.text}>{emailError}</Text>
</View>
)
}
}
const styles = StyleSheet.create({
text: {
padding: 10,
fontSize: 42
},
container: {
padding: 10
}
});
export default Home;
Related
I am not using class, I'd like to learn how to do it manually. I am dealing with login screen.
https://snack.expo.io/#ericsia/call-function-from-child-component
If you want to show me your code, you need to save and share the link. So I wanted a functional component for display textbox (assume ChildComponent as the function name, so export ChildComponent).
So in Parent/Screen1 I have something like this right?
import * as React from 'react';
import { Text, View, StyleSheet, TouchableOpacity } from 'react-native';
import Constants from 'expo-constants';
// You can import from local files
import ChildComponent from './components/ChildComponent';
export default function App() {
function checkSuccess()
{
// call helloWorld from here
}
return (
<View style={styles.container}>
<ChildComponent />
<TouchableOpacity style={styles.button}
onPress={ checkSuccess } >
<Text>helloWorld ChildComponent</Text>
</TouchableOpacity>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
paddingTop: Constants.statusBarHeight,
backgroundColor: '#ecf0f1',
padding: 8,
},
button: {
alignItems: "center",
backgroundColor: "#DDDDDD",
padding: 10
},
});
so if the result is invalid right, I wanted to display a tiny red error message.
something like this
My approach is if I can call a function from the ChildComponent then I may still solve it.
I googled it and most of the solution provided is for class.
I tried useEffect React.createRef useImperativeHandle but I didn't get it work.
for start, i am just trying to call the helloWorld()
import * as React from 'react';
import { TextInput , View, StyleSheet, Image } from 'react-native';
export default function ChildComponent() {
function helloWorld()
{
alert("Hello World");
}
return (<TextInput placeholder="Try to call helloWorld from App.js"/>);
}
Another question, if I have a textbox in my ChildComponent how do I retrieve the text/value from parent?
The Easy Way: Passing Props
You can move the helloWorld function up to the parent component and pass it down to the child as a prop. That way both components can call it. I recommend using an arrow function when you are going to be passing around a function, though it doesn't matter in this case.
Parent
export default function App() {
const helloWorld = () => {
alert('Hello World');
}
const checkSuccess = () => {
helloWorld();
}
return (
<View style={styles.container}>
<ChildComponent helloWorld={helloWorld} />
<TouchableOpacity style={styles.button} onPress={checkSuccess}>
<Text>helloWorld ChildComponent</Text>
</TouchableOpacity>
</View>
);
}
Child
const ChildComponent = ({ helloWorld }) => {
// could do anything with helloWorld here
return <TextInput placeholder="Try to call helloWorld from App.js" />;
};
The Hard Way: Ref Forwarding
If you want to keep the function in the child component then you need to go through a lot of hoops. I do not recommend this approach.
You have to do all of these steps:
Create a ref object in the parent using useRef: const childRef = React.useRef();
Pass the ref to the child as a prop: <ChildComponent ref={childRef} />
Call the function on the current value of the child component ref, using ?. to avoid errors if .current has not yet been set: childRef.current?.helloWorld();
Accept the ref prop in the child by using forwardRef: React.forwardRef( (props, ref) => {
Expose the helloWorld function as an instance variable of the child component by using useImperativeHandle: React.useImperativeHandle(ref , () => ({helloWorld}));
Parent:
export default function App() {
const childRef = React.useRef();
function checkSuccess() {
childRef.current?.helloWorld();
}
return (
<View style={styles.container}>
<ChildComponent ref={childRef} />
<TouchableOpacity style={styles.button} onPress={checkSuccess}>
<Text>helloWorld ChildComponent</Text>
</TouchableOpacity>
</View>
);
}
Child:
const ChildComponent = React.forwardRef((props, ref) => {
function helloWorld() {
alert('Hello World');
}
React.useImperativeHandle(ref, () => ({ helloWorld }));
return <TextInput placeholder="Try to call helloWorld from App.js" />;
});
Edit: Expo Link
I have a TextInput component that should transform the input to capitals whilst typing. My code is as follows:
import React, {Component} from 'react';
import { View, StyleSheet, Text, TextInput, Button } from 'react-native';
export default class ProfileTest extends React.Component {
constructor(props) {
super(props);
this.state = {text : ''};
}
render() {
return (
<View>
<TextInput
style={{fontSize : 60}}
onChangeText={text => {
text = text
.toUpperCase();
this.setState({ text: text });
}}
value={this.state.text}
placeholder="enter text"
/>
</View>
)
}
}
And on Expo, this does work. However, when I try this on my Android device, I get the following behavior:
The first two letters work fine, but whenever I add a third letter, it suddenly repeats the first two letters so that
ABC -> ABABC
I have no idea why it does this and I cannot seem to get rid of it. I have identified the '.toUpperCase()' as the culprit.
Thanks for helping!
You can find more information here:
https://github.com/facebook/react-native/issues/23578
1 person made a fix for it but it is still not in production:
https://github.com/facebook/react-native/pull/29070
I tried to find some workaround like mutating state, refs but nothing works :/
import React, { useState } from 'react';
import { TextInput, Platform } from 'react-native';
const UselessTextInput = () => {
const [info, setInfo] = useState('');
return (
<TextInput
style={{ height: 40, borderColor: 'gray', borderWidth: 1 }}
onChangeText={text => {
setInfo(text.toLowerCase())
}}
secureTextEntry={Platform.OS === 'ios' ? false : true}
keyboardType={Platform.OS === 'ios' ? null : 'visible-password'}
value={info}
/>
);
}
export default UselessTextInput;
I have the following react-native code. The idea is, I wanted to move all of the common functionality (in the below example, it's the save button and corresponding function into a parent class).
export default class DocumentScreen extends Component {
save() {
console.log(this.state);
}
render() {
return (
<View>
{this.props.children}
<View>
<Button onClick={this.save.bind(this)}/>
</View>
<View>
);
}
}
The children document is responsible for actually defining the fields that appear on the screen, e.g.
export default (props) => (
<DocumentScreen {...props}>
<View>
<TextInput onChangeText={(val) => this.setState({val})}/>
<View>
</DocumentScreen>
);
Unfortunately, this.setState called on changes to the text input field does not change the state of the parent object, so when I trigger the save function call defined in the DocumentScreen, it's state is null.
What's the best way for me to expose the parent's setState function in the child component?
Thank you.
Because I see you have use HOC as the answer, I think can make use of renderProps. This will be better than using HOC. You get more freedom and also you don't gonna have props naming problem. A good video where you can see the strength of it vs HOC https://youtu.be/BcVAq3YFiuc
Little example
class DocumentScreen extends Component {
state = {
value: ''
}
save() {
console.log(this.state);
}
handleChange = value => {
this.setState({ value })
}
render() {
return (
<View>
{this.props.children({ handleChange: this.handleChange })}
<View>
<Button onClick={this.save.bind(this)}/>
</View>
</View>
);
}
}
const Input = (props) => (
<DocumentScreen {...props}>
{({ handleChange }) => (
<View>
<TextInput onChangeText={handleChange}/>
<View>
)}
</DocumentScreen>
);
This is a common use case with React. The solution should be declare the handler function inside the parent component then pass it to the children. You will need to pay attention to context of the function so the this is correctly pointed to the parent function, it can be done by either use bind function in javascript or declare the event handler using arrow function. An example is as below:
export default class DocumentScreen extends Component {
changeHandler = (val) => {
this.setState({ val });
}
// Binding function inside JSX should be avoid
render() {
return (
<View>
{this.props.children}
<ChildComponent onChange={this.changeHandler} />
</View>
)
}
}
// Child Component
export default (props) => (
<DocumentScreen>
<View>
<TextInput onChangeText={props.onChange}/>
</View>
</DocumentScreen>
);
So, what I ended up doing was wrapping my parent component in a HOC function, like this:
export function wrapper(DocumentComponent) {
return class extends Component {
changeHandler = (val) => {
this.setState({ val });
}
save() {
console.log(this.state);
}
render() {
return (
<View>
<DocumentComponent onChange={this.changeHanlder}/>
<View>
<Button onClick={this.save.bind(this)}/>
</View>
<View>
);
}
};
}
And then I've used the wrapper function to pass the child to the parent (and the parent can now pass the props back to the child).
export default wrapper(
(props) => (
<View>
<TextInput onChangeText={props.onChange}/>
</View>
)
)
There is a bit of circular referencing happening here though...
I am currently learning React Native.
I just a built a very simple app to test out the Button component.
When I click on the button component the console log is printed as expected.
But after printing out the console log it pops out the following error.
**undefined is not an object (evaluating '_this2.btnPress().bind')**
I am not sure what is wrong ?
Can anyone let me know what I am doing wrong ?
import React from 'react';
import { StyleSheet, Text, View, Button } from 'react-native';
export default class App extends React.Component {
btnPress() {
console.log("Fn Button pressed");
}
render() {
return (
<View style={styles.container}>
<Button title="this is a test"
onPress={()=> this.btnPress().bind(this)} />
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
});
You are invoking the function instead of passing a reference through bind.
loose the ().
And you should not wrap it with an arrow function as bind is already returning a new function instance
onPress={this.btnPress.bind(this)} />
By the way, this will return and create a function instance on each render, you should do it once in the constructor (which runs only once):
export default class App extends React.Component {
constructor(props){
super(props);
this.btnPress = this.btnPress.bind(this);
}
btnPress() {
console.log("Fn Button pressed");
}
render() {
return (
<View style={styles.container}>
<Button title="this is a test"
onPress={this.btnPress} />
</View>
);
}
}
Or use an arrow function which uses a lexical context for this:
export default class App extends React.Component {
btnPress = () => {
console.log("Fn Button pressed");
}
render() {
return (
<View style={styles.container}>
<Button title="this is a test"
onPress={this.btnPress} />
</View>
);
}
}
i am new in react-native and i want to press to to specific item in ListView, but when i click to item wich i want to select i didn't get console log message and i didn't get any errors so my code look like this
in renderRow my code look like this
renderRow(record) {
return (
<View style={styles.row}>
<TouchableHighlight onPress={() => this._pressRow()}>
<View style={styles.info}>
<Text style={styles.items}>{record.nom}</Text>
</View>
</TouchableHighlight>
</View>
);
}
and _pressRow function simple console log
_pressRow (rowID: number) {
console.log("clicked");
}
and render function
render() {
return (
<ScrollView scrollsToTop={false} style={styles.menu}>
<ListView
dataSource={this.state.dataSource}
renderRow={this.renderRow}
/>
</ScrollView>
);
}
how can i resolve this issue and thanks.
Are you using the autobind-decorator? Using your code as is the _pressRow method won't be triggered. When I add the autobind decorator or change _pressRow into a fat-arrow function the console.log does work for me:
import React, { Component } from 'react'
import { View, TouchableHighlight, Text, ScrollView, ListView } from 'react-native'
_pressRow = (rowID: number) => {
console.log("clicked")
}
class App extends Component {
constructor(props) {
super(props)
this.dataSource = new ListView.DataSource({
rowHasChanged: (r1, r2) => r1 !== r2,
})
this.state = {
dataSource: this.dataSource.cloneWithRows([
{ nom: 'a' },
{ nom: 'b' },
]),
}
}
renderRow(record) {
return (
<View>
<TouchableHighlight onPress={() => this._pressRow()}>
<View>
<Text>{record.nom}</Text>
</View>
</TouchableHighlight>
</View>
)
}
render() {
return (
<ScrollView scrollsToTop={false}>
<ListView
dataSource={this.state.dataSource}
renderRow={this.renderRow}
/>
</ScrollView>
)
}
}
export default App
change the this._pressRow() to this._pressRow.bind(this) if your function is in your Class
I used TouchableHighlight to wrap "un-pressable" component (accompany with changing this._pressRow to this._pressRow.bind(this)).
Moreover, some component (such as Text component) does not fill all the space of ListView row. So that onPress only works if you press right at text (it does not work if you press on the row location that does not have any text). So that it is useful to wrap with TouchableHighlight component.