I am trying to set up some inputs in React Native as shown in the code below, however I have a weird bug where I can click the input box, the keyboard shows up, but if I click the keyboard it doesn't do anything, I can't even e.g. press shift or open numbers section. The issue is definitely not displaying the value as it changes when I change "name" state for example.
Why is my keyboard in React native completely unresponsive?
EDIT: So it seems if I run it in the web version, inputs are completely fine and everything works, my keyboard is only not working in the Expo Go build of the app, can anyone help with this?
import { useState } from "react";
import {
Button,
StyleSheet,
TextInput,
ScrollView,
ActivityIndicator,
View,
} from "react-native";
import firebase from "../database/firebaseDb";
const AddUserScreen = () => {
const [dbRef, setDbRef] = useState(firebase.firestore().collection("users"));
const [name, setName] = useState("namehi");
const [email, setEmail] = useState("");
const [mobile, setMobile] = useState("");
const [isLoading, setIsLoading] = useState(false);
const storeUser = () => {
if (name === "") {
alert("Please fill in at least your name.");
} else {
setIsLoading(true);
dbRef
.add({
name: name,
email: email,
mobile: mobile,
})
.then((res) => {
setName("");
setEmail("");
setMobile("");
setIsLoading(false);
this.props.navigation.navigate("UserScreen");
})
.catch((err) => {
console.error("Error found: ", err);
setIsLoading(false);
});
}
};
if (isLoading) {
return (
<View style={styles.preloader}>
<ActivityIndicator size="large" color="#9E9E9E" />
</View>
);
}
return (
<ScrollView style={styles.container}>
<View style={styles.inputGroup}>
<TextInput
placeholder={"Name"}
value={name}
onChangeText={(val) => {
console.log(val); // <<<<<<<< This console.log doesn't fire at all
setName("it worked");
}}
/>
</View>
<View style={styles.inputGroup}>
<TextInput
multiline={true}
numberOfLines={4}
placeholder={"Email"}
value={email}
onChangeText={setEmail}
/>
</View>
<View style={styles.inputGroup}>
<TextInput
placeholder={"Mobile"}
value={mobile}
onChangeText={setMobile}
/>
</View>
<View style={styles.button}>
<Button title="Add User" onPress={() => storeUser()} color="#19AC52" />
</View>
</ScrollView>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 35,
},
inputGroup: {
flex: 1,
padding: 0,
marginBottom: 15,
borderBottomWidth: 1,
borderBottomColor: "#cccccc",
},
preloader: {
left: 0,
right: 0,
top: 0,
bottom: 0,
position: "absolute",
alignItems: "center",
justifyContent: "center",
},
});
export default AddUserScreen;
you have to use the arrow function to call setEmail
const onChangeEmailHandler = (t) => {
setEmail(t)
}
.
.
.
<TextInput
multiline={true}
numberOfLines={4}
placeholder={"Email"}
value={email}
onChangeText={onChangeEmailHandler}
/>
Eventually this started working for me after uninstalling and reinstalling the app on my phone multiple times, seems to be a problem with Expo Go and not the code.
Related
I have a React Native textInput and a button and I would like to remove the focus from the textInput if I were to click on the button. Would that be possible?
My TextInput looks like this:
<TextInput
onChangeText={onChange}
value={searchQuery}
placeholder="Start typing to search or add your own"
maxLength={80}
onFocus={() => {
setIsInputFocused(true);
}}
onBlur={() => {
setIsInputFocused(false);
setSearchQuery('');
}}
blurOnSubmit={true}
/>
And then I have a pressable element:
<View onPress={() => { //remove focus from Text Input }></View>
The useImperativeHandle hook is the one you need here, And forwardRef, useRef hooks. I have created a new TextInput called ControlledTextInput to pass a react ref to it and expose the blur method outside (App component level). Note that there are two refs one is coming as a prop(to which we bind the functions) and the one inside ControlledTextInput(which is the ref to the actual text input)
import React, { useImperativeHandle, forwardRef, useRef } from "react";
import { StyleSheet, Text, View, TextInput, Button } from "react-native";
const ControlledTextInput = forwardRef((props, ref) => {
const internalInputRef = useRef();
useImperativeHandle(ref, () => ({
blur: () => {
internalInputRef.current.blur();
}
}));
return (
<TextInput
placeholder={"Type something"}
style={styles.textInputStyle}
ref={internalInputRef}
/>
);
});
const App = () => {
const inputRef = useRef(null);
return (
<View style={styles.app}>
<View style={styles.header}>
<Text style={styles.title}>Focus lose when button click</Text>
</View>
<ControlledTextInput ref={inputRef} />
<Button title="Click ME" onPress={() => inputRef.current.blur()} />
</View>
);
};
const styles = StyleSheet.create({
app: {
marginHorizontal: "auto",
maxWidth: 500
},
header: {
padding: 20
},
title: {
fontWeight: "bold",
fontSize: "1.5rem",
marginVertical: "1em",
textAlign: "center"
},
textInputStyle: {
border: "2px solid black",
maxWidth: 500,
height: 50,
textAlign: "center",
fontSize: 20
}
});
export default App;
React Native Codesandox:
I have been trying to create a search bar all day. I finally found this guide which seemed ok: https://blog.jscrambler.com/add-a-search-bar-using-hooks-and-flatlist-in-react-native/. I followed it through using my own API and I am not getting any errors exactly, but the code in this tutorial seems unfinished.
Here is what I have:
import React, { useEffect, useState } from 'react';
import { View, Text, FlatList, SafeAreaView, TextInput } from 'react-native';
import { Card, Header } from 'react-native-elements'
import { styles } from './styles.js';
import filter from 'lodash.filter';
const FormsScreen = ({navigation, route}) => {
const [isLoading, setIsLoading] = useState(false);
const [data, setData] = useState([]);
const [query, setQuery] = useState('');
const [fullData, setFullData] = useState([]);
//Fetch all users from database
useEffect(() =>{
setIsLoading(true);
fetch('http://10.0.2.2:5000/forms').then(response =>{
if(response.ok){
return response.json();
}
}).then(data => setFullData(data)).then(setIsLoading(false));
}, []);
function renderHeader() {
return (
<View
style={{
backgroundColor: '#fff',
padding: 10,
marginVertical: 10,
borderRadius: 20
}}
>
<TextInput
autoCapitalize="none"
autoCorrect={false}
clearButtonMode="always"
value={query}
onChangeText={queryText => handleSearch(queryText)}
placeholder="Search"
style={{ backgroundColor: '#fff', paddingHorizontal: 20 }}
/>
</View>
);
}
const handleSearch = text => {
const formattedQuery = text.toLowerCase();
const filteredData = filter(fullData, form => {
return contains(form, formattedQuery);
});
setData(filteredData);
setQuery(text);
};
const contains = ({ ID }, query) => {
console.log("ID was: "+ID);
console.log("Query was: "+query);
const id = ID;
console.log('id was: '+id);
if (id.toString().includes(query)) {
return true;
}
return false;
};
if (isLoading) {
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<ActivityIndicator size="large" color="#5500dc" />
</View>
);
}
else{
return (
<SafeAreaView>
<Header
leftComponent={{ icon: 'menu', color: '#fff' }}
centerComponent={{ text: 'Request Forms', style: { color: '#fff', fontSize: 25} }}
rightComponent={{ icon: 'home', color: '#fff' }}
/>
<FlatList
ListHeaderComponent={renderHeader}
keyExtractor={(item) => item.ID.toString() }
data={fullData}
renderItem={({item}) => (
<Card>
<Card.Title>{item.ID}</Card.Title>
<Card.Divider/>
<View style={styles.Container}>
<Text>{item.Comments}</Text>
{/* <Image source={require('./System apps/Media Manager/Gallery/AppPhotos/45e5cefd-7798-4fe9-88de-86a0a15b7b9f.jpg')} /> */}
<Text>{item.RoadName}</Text>
</View>
<View style={styles.ListContainer}>
<Text style={styles.LabelText}>Name</Text>
<Text style={styles.LabelText}>Phone</Text>
<Text style={styles.LabelText}>Email</Text>
</View>
<View style={styles.ListContainer}>
<Text style={styles.CardText}>{item.Name}</Text>
<Text style={styles.CardText}>{item.Phone}</Text>
<Text style={styles.CardText}>{item.Email}</Text>
</View>
</Card>
)}
/>
</SafeAreaView>
);
}
}
export default FormsScreen;
I have 2 main problems here.
1.) The tutorial had me initialize data and setData. setData is called and it looks to me like that is the final result after the search. The problem is that the author never actually used the variable data so I what do I do with it? Right now the the list is unaltered no matter what happens.
2.) I know he is using a different API so instead of filtering through First name, Last name, and Email I am only searching through ID. In this section of the tutorial:
const contains = ({ name, email }, query) => {
const { first, last } = name;
if (first.includes(query) || last.includes(query) || email.includes(query)) {
return true;
}
return false;
};
How does this code relate first and last to the first and last name values in the data? When I use this code but substitute name with ID and therefor first with id the value of query is correct (19 for example) but the value of ID is 2040 which is not the value I am looking for, but 2040 is the last ID in the database, or in other words the most recently entered row.
This is a sample of my data for reference:
Any help is greatly appreciated.
Please update
data={fullData}
to
data={query ? data : fullData} in flat list props. That should display your filtered data whenever search query updated.
I want to use firebase phone verification without recaptcha in react native. Below is my code. The code is working properly. But I Don't want to use recaptcha. I try deleting it but it gives error. I am new to react native. Please give proper solution of the following problem. My code is given Below Check It and give solution. Thank You
import * as React from 'react';
import { Text, View, StyleSheet, TextInput,Image,TouchableOpacity,ActivityIndicator,Alert } from 'react-native';
import * as FirebaseRecaptcha from "expo-firebase-recaptcha";
import * as firebase from "firebase";
// PROVIDE VALID FIREBASE CONFIG HERE
// https://firebase.google.com/docs/web/setup
const FIREBASE_CONFIG: any = {
};
try {
if (FIREBASE_CONFIG.apiKey) {
firebase.initializeApp(FIREBASE_CONFIG);
}
} catch (err) {
// ignore app already initialized error on snack
}
export default function App() {
const recaptchaVerifier = React.useRef(null);
const verificationCodeTextInput = React.useRef(null);
const [phoneNumber, setPhoneNumber] = React.useState("");
const [verificationId, setVerificationId] = React.useState("");
const [verifyError, setVerifyError] = React.useState<Error>();
const [verifyInProgress, setVerifyInProgress] = React.useState(false);
const [verificationCode, setVerificationCode] = React.useState("");
const [confirmError, setConfirmError] = React.useState<Error>();
const [confirmInProgress, setConfirmInProgress] = React.useState(false);
const isConfigValid = !!FIREBASE_CONFIG.apiKey;
return (
<View style={styles.container}>
<FirebaseRecaptcha.FirebaseRecaptchaVerifierModal
ref={recaptchaVerifier}
firebaseConfig={FIREBASE_CONFIG}
/>
<View style={styles.first}>
<Text style={{color:'white',fontSize:25,fontWeight:'bold'}}>Welcome</Text>
</View>
<View style={styles.second}>
<Text style={{paddingVertical:5}}>Phone No</Text>
<View style={styles.fileds}>
<Image style={styles.logo} source={require('./assets/user.png')} />
<TextInput style={styles.input}
autoFocus={isConfigValid}
autoCompleteType="tel"
keyboardType="phone-pad"
textContentType="telephoneNumber"
editable={!verificationId}
onChangeText={(phoneNumber: string) => setPhoneNumber(phoneNumber)}
/>
</View>
<TouchableOpacity style={styles.button}
disabled={!phoneNumber}
onPress={async () => {
const phoneProvider = new firebase.auth.PhoneAuthProvider();
try {
setVerifyError(undefined);
setVerifyInProgress(true);
setVerificationId("");
const verificationId = await phoneProvider.verifyPhoneNumber(
phoneNumber,
recaptchaVerifier.current
);
setVerifyInProgress(false);
setVerificationId(verificationId);
verificationCodeTextInput.current?.focus();
} catch (err) {
setVerifyError(err);
setVerifyInProgress(false);
}
}}
>
<Text style={{alignSelf:'center',color:'white'}}>{`${verificationId ? "Resend" : "Send"} OTP`}</Text>
</TouchableOpacity>
{verifyError && (
<Text style={styles.error}>{`Error: ${verifyError.message}`}</Text>
)}
{verifyInProgress && <ActivityIndicator style={styles.loader} />}
{verificationId ? (
<Text style={styles.success}>
A verification code has been sent to your phone
</Text>
) : undefined}
<Text style={{paddingTop:25,paddingBottom:5}}>OTP</Text>
<View style={styles.fileds}>
<Image style={styles.logo} source={require('./assets/password.png')} />
<TextInput
ref={verificationCodeTextInput}
style={styles.input}
editable={!!verificationId}
placeholder="123456"
onChangeText={(verificationCode: string) =>
setVerificationCode(verificationCode)
}
/>
</View>
<TouchableOpacity style={styles.button}
disabled={!verificationCode}
onPress={async () => {
try {
setConfirmError(undefined);
setConfirmInProgress(true);
const credential = firebase.auth.PhoneAuthProvider.credential(
verificationId,
verificationCode
);
const authResult = await firebase
.auth()
.signInWithCredential(credential);
setConfirmInProgress(false);
setVerificationId("");
setVerificationCode("");
verificationCodeTextInput.current?.clear();
Alert.alert("Phone authentication successful!");
} catch (err) {
setConfirmError(err);
setConfirmInProgress(false);
}
}}>
<Text style={{alignSelf:'center',color:'white'}}>Confirm OTP</Text>
</TouchableOpacity>
{confirmError && (
<Text style={styles.error}>{`Error: ${confirmError.message}`}</Text>
)}
{confirmInProgress && <ActivityIndicator style={styles.loader} />}
</View>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
first: {
height:'30%',
width:'100%',
backgroundColor:'rgb(26, 47, 94)',
justifyContent:'center',
padding:20
},
second:{
paddingVertical:30,
paddingHorizontal:20,
borderTopLeftRadius:15,
borderTopRightRadius:15,
marginTop:-10,
backgroundColor:'white'
},
fileds:{
flexDirection:'row',
borderBottomColor:'grey',
borderBottomWidth:1,
padding:5,
},
logo:{
height:20,
width:20
},
button:{
backgroundColor:'rgb(72, 126, 241)',
padding:10,
borderRadius:10,
marginVertical:15
},
buttontwo:{
borderColor:'rgb(72, 126, 241)',
borderWidth:1,
padding:10,
borderRadius:10,
marginVertical:15
},
input:{
width:'80%'
},
error: {
marginTop: 10,
fontWeight: "bold",
color: "red",
},
success: {
marginTop: 10,
fontWeight: "bold",
color: "blue",
},
loader: {
marginTop: 10,
},
});
I solved it. I have a better way to do this.
and also add SHA-256 fingerprint to your firebase project.
go to -> https://console.cloud.google.com -> select project -> API & SERVICES.
Then search Android Device Verification and enable it.
That's it !!
You can't remove captcha verification using default authentication.
Use anonymous authentication to avoid captcha letters to appear.
https://firebase.google.com/docs/auth/web/anonymous-auth
But You can also make it invisible like this:
window.recaptchaVerifier = new firebase.auth.RecaptchaVerifier(
"recaptcha-container", {
size: "invisible"
}
);
you can simply use
<FirebaseRecaptchaVerifierModal
ref={/* store ref for later use */}
firebaseConfig={/* firebase web config */}
attemptInvisibleVerification={true} /* this add a true state for the verfication */
/>
I'm starting a new React Native project, I used 'expo init' and chose a blank managed project as my template. I have a couple screens and components from a different project, that I'd like to copy over into my new one. I'm receiving the following error:
Invariant Violation: Element type is invalid: expected a string (for
built-in components) or a class/function (for composite components)
but got: undefined. You likely forgot to export your component from
the file it's defined in, or you might have mixed up default and named
imports.
Check the render method of CreateAccountForm.
I can't figure out what's going on. I'm pretty sure I have everything setup exactly as I did in my first project which renders everything fine. I'm using React Navigation and my new project renders the "HomeScreen" fine as the "initialRouteName". However, whenever I try to set the initial route to 'CreateNewAccountScreen' I receive the error above.
I've tested it, and the 'CreateNewAccountScreen' will render propery as my initial route, as long as it's not trying to render the 'CreateAccountForm' component nested inside of it. Upon replacing the <CreateAccountForm> component with a simple <Text>Hi!<Text>, it rendered the screen fine with no problem, along with the <Advertisement> component.
HomeScreeen:
import React from 'react';
import { StyleSheet, Image, Button, View } from 'react-native';
import Advertisement from '../components/Advertisement';
const HomeScreen = ({navigation}) => {
return (
<View style={styles.mainContainer}>
<View style={styles.logoContainer}>
<Image style={styles.logo}
source={require('../assets/TPLookupLogo.png')}
style={{height: 200, width: 350, marginBottom: 40}}
resizeMode="contain">
</Image>
</View>
<View style={styles.btnsContainer}>
<Button
style={styles.button}
appearance="outline"
onPress={()=>{console.log('To New User')}}
title='New User'
/>
<Button
style={styles.button}
appearance="outline"
onPress={()=>{console.log('To Login')}}
title='Login'
/>
</View>
<View style={styles.adContainer}>
<Advertisement/>
</View>
</View>
);
}
const styles = StyleSheet.create({
mainContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
logoContainer: {
flex: 4,
justifyContent: 'flex-end',
alignItems: 'center'
},
btnsContainer: {
flex: 4,
width: '40%',
justifyContent: 'flex-start',
},
button: {
marginVertical: 4,
},
adContainer: {
flex: 1,
justifyContent: 'center',
backgroundColor: 'black'
}
})
export default HomeScreen;
AppNavigator:
import { createStackNavigator } from 'react-navigation-stack';
import HomeScreen from '../screens/HomeScreen';
import CreateNewAccountScreen from '../screens/CreateNewAccountScreen';
const AppNavigator = createStackNavigator(
{
Home: HomeScreen,
CreateNewAccount: CreateNewAccountScreen
},
{
initialRouteName: 'CreateNewAccount'
}
)
export default AppNavigator;
CreateNewAccountScreen:
import React from 'react';
import { StyleSheet, View } from 'react-native'
import CreateAccountForm from '../components/CreateAccountForm';
import Advertisement from '../components/Advertisement';
const CreateNewAccountScreen = () => {
return (
<View style={styles.mainContainer}>
<View style={styles.formContainer}>
<CreateAccountForm/>
</View>
<View style={styles.adContainer}>
<Advertisement/>
</View>
</View>
);
}
const styles = StyleSheet.create({
mainContainer:{
flex: 1,
},
formContainer: {
flex: 8,
},
adContainer: {
flex: 1,
justifyContent: 'center',
backgroundColor: 'black'
}
})
CreateNewAccountScreen.navigationOptions = {
headerTitle: 'Create Account'
}
export default CreateNewAccountScreen;
CreateAccountForm:
import React, { useState } from 'react';
import { StyleSheet, View, Input, Button } from 'react-native';
const CreateAccountForm = () => {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [company, setCompany] = useState('');
const [firstName, setFirstName] = useState('');
const [lastName, setLastName] = useState('');
const [address, setAddress] = useState('');
const [city, setCity] = useState('');
const [stateName, setStateName] = useState('');
const [zip, setZip] = useState('');
const onChangeEmailHandler = value => {
setEmail(value);
}
const onChangePasswordHandler = value => {
setPassword(value);
}
const onChangeCompanyHandler = value => {
setCompany(value);
}
const onChangeFirstNameHandler = value => {
setFirstName(value);
}
const onChangeLastNameHandler = value => {
setLastName(value);
}
const onChangeAddressHandler = value => {
setAddress(value);
}
const onChangeCityHandler = value => {
setCity(value);
}
const onChangeStateNameHandler = value => {
setStateName(value)
}
const onChangeZipHandler = value => {
setZip(value);
}
const RegisterUserHandler = props => {
let emailLength = email.length;
let passwordLength = password.length;
if (emailLength === 0 || passwordLength === 0)
{
console.log('Email & Password cannot be blank.');
}
else
{
registerUser()
}
}
async function registerUser () {
let headers = {
'X-Authorization': "",
'Accept': 'application/json',
'Content-Type': 'application/json',
};
let body = JSON.stringify({
Email: email,
Password: password,
Company: company,
FirstName: firstName,
LastName: lastName,
Address: address,
City: city,
State: stateName,
Zipcode: zip
})
let response = await fetch('',
{
method: 'POST',
headers: headers,
body: body
});
let responseJson = await response.json()
}
return (
<View style={styles.mainContainer}>
<Input
style={styles.input}
type="text"
value={email}
placeholder="Email"
onChangeText={onChangeEmailHandler}
/>
<Input
style={styles.input}
type="text"
value={password}
placeholder="Password"
onChangeText={onChangePasswordHandler}
/>
<Input
style={styles.input}
type="text"
value={company}
placeholder="Company"
onChangeText={onChangeCompanyHandler}
/>
<Input
style={styles.input}
value={firstName}
placeholder="First Name"
onChangeText={onChangeFirstNameHandler}
/>
<Input
style={styles.input}
value={lastName}
placeholder="Last Name"
onChangeText={onChangeLastNameHandler}
/>
<Input
style={styles.input}
value={address}
placeholder="Address"
onChangeText={onChangeAddressHandler}
/>
<View style={styles.rowInputsContainer}>
<Input
style={styles.input}
value={city}
style={styles.rowInput}
placeholder="City"
onChangeText={onChangeCityHandler}
/>
<Input
style={styles.input}
value={stateName}
style={{...styles.rowInput, ...styles.centerRowInput}}
placeholder="State"
onChangeText={onChangeStateNameHandler}
/>
<Input
style={styles.input}
value={zip}
style={styles.rowInput}
placeholder="Zip"
onChangeText={onChangeZipHandler}
/>
</View>
<Button
style={styles.btn}
onPress={RegisterUserHandler}
title='Register'
/>
</View>
)
}
const styles = StyleSheet.create({
mainContainer: {
flex: 1,
width: '75%',
alignSelf: 'center',
justifyContent: 'center',
},
rowInputsContainer: {
display: 'flex',
flexDirection: 'row',
marginBottom: 16
},
rowInput: {
flexGrow: 1,
},
centerRowInput: {
marginHorizontal: 4
},
input: {
marginVertical: 8
}
})
export default CreateAccountForm;
This exact same setup renders everything fine with no problem in my first application. So I don't understand where I went wrong. Any help, greatly appreciated, thanks, peace!
React Native has TextInput component and not Input component. Can you please check while importing it in CreateAccountForm.
I'm trying to create a passcode protected screen. The screen will uses 4 numeric input as the passcode.
The way I'm doing this is create a TextInput Component and call it 4 times in my main screen.
The problem I'm having is the TextInputs will not focus on the next one as I type the value of the previous TextInput.
I'm using refs for all PasscodeTextInput component (I've been informed that it is a legacy method but I do not know any other way, alas).
Tried this method(without creating my own component), no luck too.
METHOD
index.ios.js
import React, { Component } from 'react';
import { AppRegistry, TextInput, View, Text } from 'react-native';
import { PasscodeTextInput } from './common';
export default class ProgressBar extends Component {
render() {
const { centerEverything, container, passcodeContainer, textInputStyle} = styles;
return (
<View style={[centerEverything, container]}>
<View style={[passcodeContainer]}>
<PasscodeTextInput
autoFocus={true}
ref="passcode1"
onSubmitEditing={(event) => { this.refs.passcode2.focus() }} />
<PasscodeTextInput
ref="passcode2"
onSubmitEditing={(event) => { this.refs.passcode3.focus() }} />
<PasscodeTextInput
ref="passcode3"
onSubmitEditing={(event) => { this.refs.passcode4.focus() }}/>
<PasscodeTextInput
ref="passcode4" />
</View>
</View>
);
}
}
const styles = {
centerEverything: {
justifyContent: 'center',
alignItems: 'center',
},
container: {
flex: 1,
backgroundColor: '#E7DDD3',
},
passcodeContainer: {
flexDirection: 'row',
},
}
AppRegistry.registerComponent('ProgressBar', () => ProgressBar);
PasscodeTextInput.js
import React from 'react';
import {
View,
Text,
TextInput,
Dimensions
} from 'react-native';
const deviceWidth = require('Dimensions').get('window').width;
const deviceHeight = require('Dimensions').get('window').height;
const PasscodeTextInput = ({ ref, autoFocus, onSubmitEditing, onChangeText, value}) => {
const { inputStyle, underlineStyle } = styles;
return(
<View>
<TextInput
ref={ref}
autoFocus={autoFocus}
onSubmitEditing={onSubmitEditing}
style={[inputStyle]}
maxLength={1}
keyboardType="numeric"
placeholderTextColor="#212121"
secureTextEntry={true}
onChangeText={onChangeText}
value={value}
/>
<View style={underlineStyle} />
</View>
);
}
const styles = {
inputStyle: {
height: 80,
width: 60,
fontSize: 50,
color: '#212121',
fontSize: 40,
padding: 18,
margin: 10,
marginBottom: 0
},
underlineStyle: {
width: 60,
height: 4,
backgroundColor: '#202020',
marginLeft: 10
}
}
export { PasscodeTextInput };
Update 1
index.ios.js
import React, { Component } from 'react';
import { AppRegistry, TextInput, View, Text } from 'react-native';
import { PasscodeTextInput } from './common';
export default class ProgressBar extends Component {
constructor() {
super()
this.state = {
autoFocus1: true,
autoFocus2: false,
autoFocus3: false,
autoFocus4: false,
}
}
onTextChanged(t) { //callback for immediate state change
if (t == 2) { this.setState({ autoFocus1: false, autoFocus2: true }, () => { console.log(this.state) }) }
if (t == 3) { this.setState({ autoFocus2: false, autoFocus3: true }, () => { console.log(this.state) }) }
if (t == 4) { this.setState({ autoFocus3: false, autoFocus4: true }, () => { console.log(this.state) }) }
}
render() {
const { centerEverything, container, passcodeContainer, testShit, textInputStyle } = styles;
return (
<View style={[centerEverything, container]}>
<View style={[passcodeContainer]}>
<PasscodeTextInput
autoFocus={this.state.autoFocus1}
onChangeText={() => this.onTextChanged(2)} />
<PasscodeTextInput
autoFocus={this.state.autoFocus2}
onChangeText={() => this.onTextChanged(3)} />
<PasscodeTextInput
autoFocus={this.state.autoFocus3}
onChangeText={() => this.onTextChanged(4)} />
<PasscodeTextInput
autoFocus={this.state.autoFocus4} />
</View>
</View>
);
}
}
const styles = {
centerEverything: {
justifyContent: 'center',
alignItems: 'center',
},
container: {
flex: 1,
backgroundColor: '#E7DDD3',
},
passcodeContainer: {
flexDirection: 'row',
},
}
AppRegistry.registerComponent('ProgressBar', () => ProgressBar);
There is a defaultProp for TextInput where one can focus after component mounted.
autoFocus
If true, focuses the input on componentDidMount, the default value is false. for more information please read the related Docs.
UPDATE
After componentDidUpdate it won't work properly. In that case, one can use ref to focus programmatically.
You cannot forward the ref to <TextInput> using that way because ref is one of the special props. Thus, calling this.refs.passcode2 will return you <PasscodeTextInput> instead.
Try change to the following to get the ref from <TextInput>.
PasscodeTextInput.js
const PasscodeTextInput = ({ inputRef, ... }) => {
...
return (
<View>
<TextInput
ref={(r) => { inputRef && inputRef(r) }}
...
/>
</View>
...
);
}
Then, assign the inputRef from <PasscodeTextInput> to a variable and use focus() to switch focus (it is not deprecated as of RN 0.41.2).
index.ios.js
return (
<PasscodeTextInput
autoFocus={true}
onChangeText={(event) => { event && this.passcode2.focus() }} />
<PasscodeTextInput
inputRef={(r) => { this.passcode2 = r }}
onChangeText={(event) => { event && this.passcode3.focus() }} />
<PasscodeTextInput
inputRef={(r) => { this.passcode3 = r }}
onChangeText={(event) => { event && this.passcode4.focus() }} />
<PasscodeTextInput
inputRef={(r) => { this.passcode4 = r }} />
);
P.S: event && this.passcode2.focus() prevents focus is switched when trying to clear the old passcode and enter a new one.
we handled this style of screen with a different approach.
Rather than manage 4 individual TextInputs and handle the navigation of focus across each one (and then back again when the user deletes a character), we have a single TextInput on screen but is invisible (ie. 0px x 0px) wide which has the focus, maxLength and keyboard configuration, etc.
This TextInput takes input from the user but can't actually been seen, as each character is typed in we render the entered text as a series simple View/Text elements, styled much similar to your screen above.
This approach worked well for us with no need to manage what the 'next' or 'previous' TextInput to focus next to.
You can use focus method onChangeText as Jason stated, in addition to that adding maxLength={1} can make you jump to the next input immediately without checking what's added. (just noticed its deprecated, but still this is how I solved my problem, and should do fine until v0.36, and this link explains how you should update the deprecated function).
<TextInput
ref="first"
style={styles.inputMini}
maxLength={1}
keyboardType="numeric"
returnKeyType='next'
blurOnSubmit={false}
placeholderTextColor="gray"
onChangeText={(val) => {
this.refs['second'].focus()
}}
/>
<TextInput
ref="second"
style={styles.inputMini}
maxLength={1}
keyboardType="numeric"
returnKeyType='next'
blurOnSubmit={false}
placeholderTextColor="gray"
onChangeText={(val) => {
this.refs['third'].focus()
}}
/>
...
Please notice that my use of refs are deprecated too, but I've just copied the code since I can guarantee you that was working back then (hopefully works now too).
Finally, the main issue with this type of implementation is, once you try to remove a number with backspace your focus will jump to next one, causing serious UX issues. However, you can listen for backspace key entry and perform something different instead of focusing to next input. So I'll leave a link here for you to further investigate if you choose to use this type of implementation.
Hacky Solution to Previously Described Issue: If you check what's entered in onChangeText prop before doing anything, you can jump to next input if the value is a number, else (that's a backspace), jump back. (Just came up with this idea, I haven't tried it.)
I think the issue is that onSubmitEditing is when you hit the "return" or "enter" key on the regular keyboard... there is not one of those buttons on the keypad.
Assuming you want each input to only have one character, you could look at the onChangeText and then check if text has length 1 and call focus if the length is indeed 1.
<TextInput
ref={input => {
this.nameOrId = input;
}}
/>
<TouchableOpacity
onPress={()=>{
this.nameOrId.focus()
}}
>
<Text>Click</Text>
</TouchableOpacity>
I solve with this code:
const VerifyCode: React.FC = ({ pass, onFinish }) => {
const inputsRef = useRef<Input[] | null[]>([]);
const [active, setActive] = useState<number>(0);
const onKeyPress = ({ nativeEvent }:
NativeSyntheticEvent<TextInputKeyPressEventData>) => {
if (nativeEvent.key === "Backspace") {
if (active !== 0) {
inputsRef.current[active - 1]?.focus();
return setActive(active - 1);
}
} else {
inputsRef.current[active + 1]?.focus();
return setActive(active + 1);
}
return null;
};
return (
<View style={styles.container}>
<StyledInput
onKeyPress={onKeyPress}
autoFocus={active === 0}
ref={(r) => {
inputsRef.current[0] = r;
}}
/>
<StyledInput
onKeyPress={onKeyPress}
autoFocus={active === 1}
ref={(r) => {
inputsRef.current[1] = r;
}}
/>
<StyledInput
onKeyPress={onKeyPress}
autoFocus={active === 2}
ref={(r) => {
inputsRef.current[2] = r;
}}
/>
<StyledInput
onKeyPress={onKeyPress}
autoFocus={active === 3}
ref={(r) => {
inputsRef.current[3] = r;
}}
/>
</View>
);
};
export default VerifyCode;
I put one ref in all the inputs, and when the onKeyPress fire, the function verify if have to go back or go to next input
Solved it by removing autoFocus={true} and setting timeout.
I have a popup as a functional component and using "current.focus()" with Refs like this:
const Popup = ({ placeholder, autoFocus, showStatus, }) => { const inputRef = useRef(null); useEffect(() => {
Platform.OS === 'ios'
? inputRef.current.focus()
: setTimeout(() => inputRef.current.focus(), 40); }, [showStatus]); return (
<View style={styles.inputContainer}>
<TextInput
style={styles.inputText}
defaultValue={placeholder}
ref={inputRef}
/>
</View> };