FlatList show according to TextInput handle - react-native

I have Dynamic form in which user can add form and remove form when user start typing on first form TextInput it will give suggestion as per input. Now the problem is when user start typing on first TextInput field it will get suggestion but when user add another form by clicking on addForm Button and when user start typing on new form it will get suggestion but on same time in the first form it also start giving suggestion and same if there is three form it will start giving suggestion for all three form if user start typing in one form.I want to say that If user type any of form it will give suggestion on all form.
I want like if user is on first form then it will give suggestion only for first form not for second form as well. if user is on second form it will only get suggestion on second form not on first as well.
You can see in above picture it is giving suggestion for both form even if I'm typing on second form
import React, { PureComponent } from 'react'
import {
View,
TextInput,
ScrollView,
KeyboardAvoidingView,
StyleSheet,
Picker,
ListView,
FlatList
} from 'react-native'
import { getStockItems } from "../../actions/getIndentsAction";
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { TouchableOpacity } from 'react-native-gesture-handler';
import { CardSection, Text, Button, Block, Input } from '../../components';
import { theme } from '../../constants';
import { MaterialIcons,AntDesign,Entypo } from '#expo/vector-icons';
import { CardItem,Content, ListItem,Icon,Card, Left, Body, Right } from 'native-base';
class IndentForm extends PureComponent {
static navigationOptions = ({ navigation }) => {
const { params = {} } = navigation.state;
return {
headerRight: (
<TouchableOpacity onPress={() => params.handleSave()}>
<AntDesign
name='plus'
style={{ paddingRight:10}}
size={25}
color='white'
/>
</TouchableOpacity>
)
};
};
constructor(props) {
super(props);
this.state = {
companyName:'',
formFields:[{
Item_Description:'',
Quantity:'',
}],
searchedForm:[]
};
}
componentDidMount() {
this.props.navigation.setParams({ handleSave: this.addInput});
this.props.getStockItems()
}
//add dynamic form
addInput = () => {
const existingFormFields = this.state.formFields.map(fields => ({...fields}))
const allFormFieldsAfterAddingNew = [...existingFormFields, {Item_Description: '', Quantity:''}]
this.setState({formFields: allFormFieldsAfterAddingNew})
}
//remove dynamic form
removeInput = index => () => {
this.setState({
formFields: this.state.formFields.filter((s, sidx) => index !== sidx)
});
};
//on Item Descriptionchange
onItemDescriptionChange = (text, index) => {
const { stocks } = this.props.indent;
const existingFormFields = this.state.formFields.map(fields => ({...fields}))
let targetField = {...existingFormFields[index]}
targetField.Item_Description = text
existingFormFields[index] = targetField
var searchedForm = stocks.filter(function(stock) {
return stock.Item.toLowerCase().indexOf(text.toLowerCase()) > -1;
});
this.setState({searchedForm: searchedForm , formFields: existingFormFields})
}
//on Quantity change
onQuantityChange = (text, index) => {
const existingFormFields = this.state.formFields.map(fields => ({...fields}))
let targetField = {...existingFormFields[index]}
targetField.Quantity = text
existingFormFields[index] = targetField
this.setState({formFields: existingFormFields})
}
itemSelect = (item,index) => {
const existingFormFields = this.state.formFields.map(fields => ({...fields}))
let targetField = {...existingFormFields[index]}
targetField.Item_Description = item.Item
existingFormFields[index] = targetField
this.setState({searchedForm:[], formFields:existingFormFields})
console.log("hello" + " " + item.Item + " " + index);
}
onsubmit = () => {
const data = {
companyName:this.state.companyName,
formFields:this.state.formFields
}
console.log(data)
}
render() {
const { stocks } = this.props.indent;
return (
<KeyboardAvoidingView style={{flex:1, justifyContent:"center"}} behavior="padding">
<ScrollView
showsVerticalScrollIndicator={false}
>
<Block padding={[5]}>
<Card>
<Picker
style={{flex:1}}
selectedValue={this.state.companyName}
onValueChange={(companyName)=>this.setState({companyName:companyName})}
>
<Picker.Item label='developer' value="0" />
<Picker.Item label="Developer" value="Developer" />
<Picker.Item label="Junior Develope" value="Junior Develope" />
</Picker>
</Card>
{
this.state.formFields.map((field, index) => {
return(
<Card key={index} >
<CardItem bordered>
<Left>
<Text bold>Items no : {index + 1}</Text>
</Left>
<Right>
<TouchableOpacity
onPress={this.removeInput(index)}
>
<Entypo
name="cross"
size={20}
color='#E46932'
/>
</TouchableOpacity>
</Right>
</CardItem>
<Block padding={[0, theme.sizes.base]}>
<Block>
<Input
label="description"
style={[styles.input]}
value={field.Item_Description}
onChangeText={(text)=> this.onItemDescriptionChange(text, index)}
/>
<FlatList
data={this.state.searchedForm}
keyExtractor={(ItemId,index) => index.toString()}
renderItem={({item,index})=>(
<ListItem
button={true}
key={index}
onPress={()=>this.itemSelect(item,index)}
>
<Text bold>{item.Item}</Text>
</ListItem>
)}
/>
</Block>
<Input
label="Quantity"
style={[styles.input]}
value={field.Quantity}
onChangeText={(text)=> this.onQuantityChange(text, index)}
/>
</Block>
</Card>
)
})
}
<Block padding={[0, theme.sizes.base * 1.5]}>
<Button
style={styles.submitInput}
onPress={this.onsubmit}>
<Text bold white center>Submit</Text>
</Button>
</Block>
</Block>
</ScrollView>
</KeyboardAvoidingView>
)
}
}
IndentForm.propTypes = {
getStockItems: PropTypes.func.isRequired,
indent: PropTypes.object.isRequired,
};
const mapStateToProps = state => ({
indent: state.indent,
errors:state.errors
});
export default connect(
mapStateToProps,
{
getStockItems,
}
)(IndentForm);
const styles = StyleSheet.create({
input: {
borderRadius: 0,
borderWidth: 0,
borderBottomColor: theme.colors.gray2,
borderBottomWidth: StyleSheet.hairlineWidth,
marginLeft:5
},
submitInput:{
backgroundColor:"#2ecc71"
},
addInput:{
backgroundColor:"white"
},
addButton:{
alignItems:"flex-end",
position:"absolute",
right:20,
bottom:20,
},
searchBarContainerStyle: {
marginBottom: 10,
flexDirection: "row",
height: 40,
shadowOpacity: 1.0,
shadowRadius: 5,
shadowOffset: {
width: 1,
height: 1
},
backgroundColor: "rgba(255,255,255,1)",
shadowColor: "#d3d3d3",
borderRadius: 10,
elevation: 3,
marginLeft: 10,
marginRight: 10
},
selectLabelTextStyle: {
color: "#000",
textAlign: "left",
width: "99%",
padding: 10,
flexDirection: "row"
},
placeHolderTextStyle: {
color: "#D3D3D3",
padding: 10,
textAlign: "left",
width: "99%",
flexDirection: "row"
},
dropDownImageStyle: {
marginLeft: 10,
width: 10,
height: 10,
alignSelf: "center"
},
pickerStyle: {
marginLeft: 18,
elevation:3,
paddingRight: 25,
marginRight: 10,
marginBottom: 2,
shadowOpacity: 1.0,
shadowOffset: {
width: 1,
height: 1
},
borderWidth:1,
shadowRadius: 10,
backgroundColor: "rgba(255,255,255,1)",
shadowColor: "#d3d3d3",
borderRadius: 5,
flexDirection: "row"
}
})

when you do addInput, it adds new FlatList for each input. but, data of FlatList is managed by single state which is this.state.searchedForm.
So whenever onItemDescriptionChange gets called, it updates the searchedForm state and all the FlatList reflects that change.
To resolve this, either you'll have to keep the FlatList data inside formFields state as one key or you can manage different state for each input.

Related

How to save form data into AsyncStorage and then automatically send that data into mongodb when internet is connected

I am trying to save the form submitted data into AsyncStorage and then automatically send that data into mongodb.
I have tried this. But it does not work. First time when i used the setItem it sets the item but now it is not setting the item.
I i put the code above the render then it gives me an error too many renders.
code
import React from 'react';
import { Text, View, Button, StyleSheet, TextInput, TouchableOpacity, Alert, Modal } from 'react-native';
import { useNetInfo } from '#react-native-community/netinfo';
import AsyncStorage from '#react-native-community/async-storage';
const App = () => {
const [text, onChangeText] = React.useState('');
const [number, onChangeNumber] = React.useState('');
const [data, setData] = React.useState('');
const [synched, setSynched] = React.useState('');
const netInfo = useNetInfo();
const submit = () => {
if (netInfo.isConnected) {
var inputData = {
"email": text,
"password": number
}
const storeData = async () => {
try {
await AsyncStorage.setItem("Data", JSON.stringify(inputData))
}
catch (e) {
Alert.alert(e)
}
}
}
else {
var inputData = {
"email": text,
"password": number
}
const storeData = async () => {
try {
await AsyncStorage.setItem("Data", JSON.stringify(inputData))
}
catch (e) {
Alert.alert(e)
}
}
}
}
if (netInfo.isConnected) {
var networkConn = <Text style={{ color: "green" }}>Back Online</Text>
}
else {
networkConn = <Text style={{ color: "red" }}>No internet</Text>
}
return (
<View>
<Modal
animationType="slide"
transparent={true}
visible={modalVisible}
onRequestClose={() => {
Alert.alert("Modal has been closed.");
setModalVisible(!modalVisible);
}}
></Modal>
<Text style={styles.text}>Signup Form</Text>
<TextInput
style={styles.input1}
onChangeText={onChangeText}
placeholder="Enter your email"
/>
<TextInput
style={styles.input1}
onChangeText={onChangeNumber}
placeholder="Enter your password"
secureTextEntry={true}
/>
<View style={styles.btnContainer}>
<TouchableOpacity>
<Button
style={{ fontSize: 20, color: 'green' }}
styleDisabled={{ color: 'red' }}
onPress={submit}
title="Sign Up"
>
</Button>
</TouchableOpacity>
</View>
<View>
<Text style={styles.text1}>{networkConn}</Text>
<Text style={styles.text1}>{synched}</Text>
<Text style={styles.text1}>{data}</Text>
</View>
</View>
)
}
const styles = StyleSheet.create({
text: {
marginTop: 50,
textAlign: 'center',
fontSize: 25,
fontWeight: 'bold',
color: 'blue'
},
text1: {
marginTop: 10,
textAlign: 'center',
fontSize: 12,
color: 'black'
},
input1: {
height: 40,
marginLeft: 50,
marginRight: 50,
marginTop: 30,
borderWidth: 1,
},
btnContainer: {
height: 100,
marginTop: 30,
width: 100,
alignSelf: "center"
},
});
export default App;
Putting data in render is not good idea.
Well you can use this flow:
Store the data offline if internet is not available but with extra parameter like isUploaded : false
Then when internet is back get data from the storage check whether the data parameter i.e isUploaded: false if it is false then upload the data and if you wanna keep it then you can otherwise remove the item from the asyncstorage.

How view saved data by AsyncStorage?

I have a code that saves and reads the value of a state through AsyncStorage, however when closing the app or changing the screen the value returns to the original state value, which in my case is zero. How do I change the screen or close the app so the value remains the last changed? What alternative to state variables?
My code is:
import React, { Component } from 'react'
import { Text, View, AsyncStorage, TextInput, StyleSheet, TouchableOpacity } from 'react-native'
export default class App extends Component {
constructor(props){
super(props);
this.state = {
txtInputData: '',
getValue: '',
}
}
saveValue = () => {
if(this.state.txtInputData){
AsyncStorage.setItem('key_default', this.state.txtInputData)
this.setState({txtInputData: ''})
alert('Data salved!')
}else{
alert('Please, fill the data!')
}
}
getValue = () => {
AsyncStorage.getItem('key_default').then(value => this.setState({getValue: value}))
}
render() {
return (
<View style={styles.mainContainer}>
<Text>Using AsyncStorage</Text>
<TextInput
style={styles.textInput}
placeholder = 'type here'
value = {this.state.txtInputData}
onChangeText={data => this.setState({txtInputData: data})}
/>
<TouchableOpacity
onPress={this.saveValue}
style={styles.TouchableOpacity}
>
<Text>Save value</Text>
</TouchableOpacity>
<TouchableOpacity
onPress={this.getValue}
style={styles.TouchableOpacity}
>
<Text> Read value</Text>
</TouchableOpacity>
<Text>Value read:{this.state.getValue}</Text>
</View>
)
}
}
const styles = StyleSheet.create({
mainContainer: {
flex: 1,
padding: 20,
},
textInput: {
marginTop: 10,
borderWidth: 1,
borderColor: '#fa19',
fontSize: 15
},
TouchableOpacity: {
width: '50%',
marginTop: 10,
borderWidth: 1,
borderColor: '#fa19',
padding: 10,
alignSelf: 'center',
alignItems: 'center'
}
})
Just setStat of txtInputData instead of getValue like this
getValue = () => {
AsyncStorage.getItem('key_default').then(value => this.setState({txtInputData: value}))
}

How to use ScrollToIndex in React Native?

I use a FlatList to display search results.
<View>
<FlatList
data={this.state.films}
keyExtractor={(item) => item.id.toString()}
renderItem={({item}) => <FilmItem film={item}/>}
/>
</View>
However, when the user starts a second search, this list does not restart to index 0. So I would like to add ScrollToIndex or viewPosition. I tried this but it doesn't work :
<View>
<FlatList
data={this.state.films}
keyExtractor={(item) => item.id.toString()}
renderItem={({item}) => <FilmItem film={item}/>}
scrollToItem={(index) => {0}}
/>
</View>
Could you please explain me why this is wrong and what would be the best solution ?
Thanks a lot,
Try this... I made a bunch of changes and have put comments:
// Components/Search.js
import React from "react";
import {StyleSheet, View, TextInput, Button, Text, FlatList, ActivityIndicator} from "react-native";
import FilmItem from "./filmItem";
import {getFilmsFromApiWithSearchedText} from "../API/TMDBApi";
class Search extends React.Component {
flatListRef = null; // declaring this here to make it explicit that we have this available
constructor(props) {
super(props);
this.searchedText = "";
this.state = {
films: [],
isLoading: false,
};
}
_loadFilms = () => {
// needs to be an arrow function so `this` is bound to this component
this.scrollToIndex(); // assumed you meant to actually call this?
if (this.searchedText.length > 0) {
this.setState({isLoading: true});
getFilmsFromApiWithSearchedText(this.searchedText).then(data => {
this.setState({
films: data.results,
isLoading: false,
});
});
}
};
// needs arrow to use `this`
_searchTextInputChanged = text => {
this.searchedText = text;
};
// needs arrow to use `this`
_displayLoading = () => {
// better to return null if not loading, otherwise return loading indicator
if (!this.state.isLoading) return null;
return (
<View style={styles.loading_container}>
<ActivityIndicator size='large' />
</View>
);
};
scrollToIndex = () => {
// you previously had this inside another method
this.flatListRef && this.flatListRef.scrollToIndex({index: 1}); // checking it exists first
};
render() {
return (
<View style={styles.main_container}>
<TextInput
style={styles.textinput}
placeholder='Titre du film'
onChangeText={text => this._searchTextInputChanged(text)}
onSubmitEditing={this._loadFilms} // no need to create a new anonymous function here
/>
<Button title='Rechercher' onPress={this._loadFilms} />
<View>
<FlatList
data={this.state.films}
ref={ref => (this.flatListRef = ref)}
keyExtractor={item => item.id.toString()}
onEndReachedThreshold={1} // corrected typo
onEndReached={() => {
console.log("TOC");
}}
renderItem={({item}) => <FilmItem film={item} />}
/>
</View>
{this._displayLoading()}
</View>
);
}
}
const styles = StyleSheet.create({
main_container: {
marginTop: 60,
padding: 15,
flex: 1,
},
loading_container: {
position: "absolute",
left: 0,
right: 0,
top: 100,
bottom: 0,
alignItems: "center",
paddingTop: 50,
backgroundColor: "white",
},
textinput: {
height: 50,
borderColor: "#999999",
borderWidth: 1,
paddingLeft: 5,
},
});
export default Search;
Using this code as example:
import React, { Component } from 'react';
import { Text, View, FlatList, Dimensions, Button, StyleSheet } from 'react-native';
const { width } = Dimensions.get('window');
const style = {
justifyContent: 'center',
alignItems: 'center',
width: width,
height: 50,
flex: 1,
borderWidth: 1,
};
const COLORS = ['deepskyblue','fuchsia', 'lightblue '];
function getData(number) {
let data = [];
for(var i=0; i<number; ++i)
{
data.push("" + i);
}
return data;
}
class ScrollToExample extends Component {
getItemLayout = (data, index) => (
{ length: 50, offset: 50 * index, index }
)
getColor(index) {
const mod = index%3;
return COLORS[mod];
}
scrollToIndex = () => {
let randomIndex = Math.floor(Math.random(Date.now()) * this.props.data.length);
this.flatListRef.scrollToIndex({animated: true, index: randomIndex});
}
scrollToItem = () => {
let randomIndex = Math.floor(Math.random(Date.now()) * this.props.data.length);
this.flatListRef.scrollToIndex({animated: true, index: "" + randomIndex});
}
render() {
return (
<View style={styles.container}>
<View style={styles.header}>
<Button
onPress={this.scrollToIndex}
title="Tap to scrollToIndex"
color="darkblue"
/>
<Button
onPress={this.scrollToItem}
title="Tap to scrollToItem"
color="purple"
/>
</View>
<FlatList
style={{ flex: 1 }}
ref={(ref) => { this.flatListRef = ref; }}
keyExtractor={item => item}
getItemLayout={this.getItemLayout}
initialScrollIndex={50}
initialNumToRender={2}
renderItem={({ item, index}) => (
<View style={{...style, backgroundColor: this.getColor(index)}}>
<Text>{item}</Text>
</View>
)}
{...this.props}
/>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1
},
header: {
paddingTop: 20,
backgroundColor: 'darkturquoise',
alignItems: 'center',
justifyContent: 'center'
}
});
export default class app extends Component {
render() {
return <ScrollToExample
data={getData(100)}
/>
}
}
I tried to add a ref but it doesn't work :
// Components/Search.js
import React from 'react'
import { StyleSheet, View, TextInput, Button, Text, FlatList, ActivityIndicator } from 'react-native'
import FilmItem from './filmItem'
import { getFilmsFromApiWithSearchedText } from '../API/TMDBApi'
class Search extends React.Component {
constructor(props) {
super(props)
this.searchedText = ""
this.state = {
films: [],
isLoading: false,
}
}
_loadFilms() {
{this.scrollToIndex}
if (this.searchedText.length > 0) {
this.setState({isLoading: true})
getFilmsFromApiWithSearchedText(this.searchedText).then(data => {
this.setState({
films: data.results,
isLoading: false
})
})
}
}
_searchTextInputChanged(text) {
this.searchedText = text
}
_displayLoading() {
if (this.state.isLoading) {
return (
<View style={styles.loading_container}>
<ActivityIndicator size='large'/>
</View>
)
}
scrollToIndex = () => {
this.flatListRef.scrollToIndex({index: 1});
}
}
render() {
return (
<View style={styles.main_container}>
<TextInput
style={styles.textinput}
placeholder='Titre du film'
onChangeText={(text) => this._searchTextInputChanged(text)}
onSubmitEditing={() => this._loadFilms()}
/>
<Button title='Rechercher' onPress={() => this._loadFilms()} />
<View>
<FlatList
data={this.state.films}
ref={(ref) => { this.flatListRef = ref; }}
keyExtractor={(item) => item.id.toString()}
onEndReachedThreashold={1}
onEndReached={() => {console.log("TOC")}}
renderItem={({item}) => <FilmItem film={item}/>}
/>
</View>
{this._displayLoading()}
</View>
)
}
}
const styles = StyleSheet.create({
main_container: {
marginTop: 60,
padding: 15,
flex: 1,
},
loading_container: {
position: 'absolute',
left: 0,
right: 0,
top: 100,
bottom: 0,
alignItems: 'center',
paddingTop: 50,
backgroundColor: 'white',
},
textinput: {
height: 50,
borderColor: '#999999',
borderWidth: 1,
paddingLeft: 5,
}
})
export default Search
I'm sur I am doing something wrong, but don't find the error. Thanks a lot for your help.

Results do not update after a change of state

I have a problem, when I do a search, I get the data from my API, the first time I do a search, everything is fine, all the data is displayed. However, when I do a second search immediately, nothing is updated.
I put in console.log, and I see that I'm getting this data back, yet the display is not updated.
import React, { Component } from "react";
import { SafeAreaView, StyleSheet } from "react-native";
import Search from "./Component/Search";
export default class App extends Component {
render() {
return (
<SafeAreaView style={styles.container}>
<Search />
</SafeAreaView>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1
}
});
import React from "react";
import { View, TextInput, Button, FlatList, StyleSheet } from "react-native";
import LivreItem from "../Component/LivreItem";
class Search extends React.Component {
constructor(props) {
super(props);
this.inputValue = "";
this.state = { livres: [] };
}
searchBooks = async () => {
const key = "&key=XXXXXXXXXXXXXXXXXXXXXXX";
const url = "https://www.googleapis.com/books/v1/volumes?q=" + this.inputValue + key;
return fetch(url)
.then(response => response.json())
.catch(e => {
console.log("Une erreur s'est produite");
console.log(e);
});
};
getBooks = () => {
if (this.inputValue.length > 0) {
this.searchBooks()
.then(data => this.setState({ livres: data.items }))
.catch(reject => console.log(reject));
}
};
changeText = text => {
this.inputValue = text;
};
render() {
return (
<View style={styles.header_container}>
<View style={styles.sub_container}>
<TextInput
onChangeText={text => this.changeText(text)}
style={styles.input}
placeholder="Ex: Harry Potter"
/>
<Button
style={styles.button}
title="Rechercher"
onPress={() => this.getBooks()}
/>
</View>
<FlatList
style={styles.list}
data={this.state.livres}
keyExtractor={(item, index) => item + index}
renderItem={({ item }) => <LivreItem livre={item.volumeInfo} />}
/>
</View>
);
}
}
const styles = StyleSheet.create({
sub_container: {
justifyContent: "space-between",
flexDirection: "row",
marginTop: 30,
paddingRight: 10,
paddingLeft: 10
},
header_container: {
flex: 1,
flexDirection: "column",
padding: 10
},
input: {
borderRadius: 4,
borderWidth: 0.5,
borderColor: "#d6d7da",
width: 150,
paddingLeft: 5
},
button: {
borderRadius: 4
},
list: {
paddingLeft: 15,
paddingRight: 15
}
});
export default Search;
import React from "react";
import { View, StyleSheet, Image, Text } from "react-native";
class LivreItem extends React.Component {
constructor(props) {
super(props);
this.state = { livre: this.props.livre};
this.description =
this.state.livre.description === null || this.state.livre.description === undefined
? "Pas de description disponible"
: this.state.livre.description;
this.img = this.state.livre.imageLinks;
this.image =
this.img === undefined ||
this.img.smallThumbnail === undefined ||
this.img.smallThumbnail === null
? null
: this.state.livre.imageLinks.smallThumbnail;
}
render() {
return (
<View style={styles.content}>
<View>
<Image style={styles.image} source={{ uri: this.image }} />
<Image style={styles.image} source={this.image} />
</View>
<View style={styles.content_container}>
<Text style={styles.titre}>{this.state.livre.title}</Text>
<Text style={styles.description} numberOfLines={4}>
{this.description}
</Text>
</View>
</View>
);
}
}
const styles = StyleSheet.create({
content: {
height: 125,
flexDirection: "row",
marginTop: 15
},
content_container: {
flexDirection: "column",
flexShrink: 1,
marginLeft: 10
},
image: {
width: 100,
height: 100
},
titre: {
fontWeight: "bold",
flexWrap: "wrap"
},
description: {
flexWrap: "wrap"
}
});
export default LivreItem;
Thanks.
Configure the prop extraData in Flatlist component ala:
<FlatList
extraData={this.state.livres}
/>
Pass a boolean value to the FlatList extraData.
<FlatList
extraData={this.state.refresh}
/>

how to design react native OTP enter screen?

I am new in react native design .Let me know how to achieve the screen shown below
is it necessary to use 4 TextInput or possible with one?
You can use just one hidden TextInput element and attach an onChangeText function and fill values entered in a Text view (you can use four different text view of design requires it).
Make sure to focus the TextInput on click of Text view if user click on it
Here I have created a screen with Six text input for otp verfication with Resend OTP functionality with counter timer of 90 sec. Fully tested on both android and ios.
I have used react-native-confirmation-code-field for underlined text input.
Below is the complete code.
import React, { useState, useEffect } from 'react';
import { SafeAreaView, Text, View ,TouchableOpacity} from 'react-native';
import { CodeField, Cursor, useBlurOnFulfill, useClearByFocusCell } from
'react-native-confirmation-code-field';
import { Button } from '../../../components';
import { styles } from './style';
interface VerifyCodeProps {
}
const CELL_COUNT = 6;
const RESEND_OTP_TIME_LIMIT = 90;
export const VerifyCode: React.FC<VerifyCodeProps> = () => {
let resendOtpTimerInterval: any;
const [resendButtonDisabledTime, setResendButtonDisabledTime] = useState(
RESEND_OTP_TIME_LIMIT,
);
//to start resent otp option
const startResendOtpTimer = () => {
if (resendOtpTimerInterval) {
clearInterval(resendOtpTimerInterval);
}
resendOtpTimerInterval = setInterval(() => {
if (resendButtonDisabledTime <= 0) {
clearInterval(resendOtpTimerInterval);
} else {
setResendButtonDisabledTime(resendButtonDisabledTime - 1);
}
}, 1000);
};
//on click of resend button
const onResendOtpButtonPress = () => {
//clear input field
setValue('')
setResendButtonDisabledTime(RESEND_OTP_TIME_LIMIT);
startResendOtpTimer();
// resend OTP Api call
// todo
console.log('todo: Resend OTP');
};
//declarations for input field
const [value, setValue] = useState('');
const ref = useBlurOnFulfill({ value, cellCount: CELL_COUNT });
const [props, getCellOnLayoutHandler] = useClearByFocusCell({
value,
setValue,
});
//start timer on screen on launch
useEffect(() => {
startResendOtpTimer();
return () => {
if (resendOtpTimerInterval) {
clearInterval(resendOtpTimerInterval);
}
};
}, [resendButtonDisabledTime]);
return (
<SafeAreaView style={styles.root}>
<Text style={styles.title}>Verify the Authorisation Code</Text>
<Text style={styles.subTitle}>Sent to 7687653902</Text>
<CodeField
ref={ref}
{...props}
value={value}
onChangeText={setValue}
cellCount={CELL_COUNT}
rootStyle={styles.codeFieldRoot}
keyboardType="number-pad"
textContentType="oneTimeCode"
renderCell={({ index, symbol, isFocused }) => (
<View
onLayout={getCellOnLayoutHandler(index)}
key={index}
style={[styles.cellRoot, isFocused && styles.focusCell]}>
<Text style={styles.cellText}>
{symbol || (isFocused ? <Cursor /> : null)}
</Text>
</View>
)}
/>
{/* View for resend otp */}
{resendButtonDisabledTime > 0 ? (
<Text style={styles.resendCodeText}>Resend Authorisation Code in {resendButtonDisabledTime} sec</Text>
) : (
<TouchableOpacity
onPress={onResendOtpButtonPress}>
<View style={styles.resendCodeContainer}>
<Text style={styles.resendCode} > Resend Authorisation Code</Text>
<Text style={{ marginTop: 40 }}> in {resendButtonDisabledTime} sec</Text>
</View>
</TouchableOpacity >
)
}
<View style={styles.button}>
<Button buttonTitle="Submit"
onClick={() =>
console.log("otp is ", value)
} />
</View>
</SafeAreaView >
);
}
Style file for this screen is in given below code:
import { StyleSheet } from 'react-native';
import { Color } from '../../../constants';
export const styles = StyleSheet.create({
root: {
flex: 1,
padding: 20,
alignContent: 'center',
justifyContent: 'center'
},
title: {
textAlign: 'left',
fontSize: 20,
marginStart: 20,
fontWeight:'bold'
},
subTitle: {
textAlign: 'left',
fontSize: 16,
marginStart: 20,
marginTop: 10
},
codeFieldRoot: {
marginTop: 40,
width: '90%',
marginLeft: 20,
marginRight: 20,
},
cellRoot: {
width: 40,
height: 40,
justifyContent: 'center',
alignItems: 'center',
borderBottomColor: '#ccc',
borderBottomWidth: 1,
},
cellText: {
color: '#000',
fontSize: 28,
textAlign: 'center',
},
focusCell: {
borderBottomColor: '#007AFF',
borderBottomWidth: 2,
},
button: {
marginTop: 20
},
resendCode: {
color: Color.BLUE,
marginStart: 20,
marginTop: 40,
},
resendCodeText: {
marginStart: 20,
marginTop: 40,
},
resendCodeContainer: {
flexDirection: 'row',
alignItems: 'center'
}
})
Hope it will be helpful for many. Happy Coding!!
I solved this problem for 6 digit otp by Following Chethan's answer.
Firstly create a array 'otp' initialised with otp = ['-','-','-','-','-','-'] in state,then create a otpVal string in state like this
const [otp, setOtp] = useState(['-', '-', '-', '-', '-', '-']);
const [otpVal, setOtpVal] = useState('');
Now the actual logic of rendering otp boxes willbe as follows.
<TextInput
onChangeText={value => {
if (isNaN(value)) {
return;
}
if (value.length > 6) {
return;
}
let val =
value + '------'.substr(0, 6 - value.length);
let a = [...val];
setOtpVal(a);
setOtp(value);
}}
style={{ height: 0 }}
autoFocus = {true}
/>
<View style={styles.otpBoxesContainer}>
{[0, 1, 2, 3, 4, 5].map((item, index) => (
<Text style={styles.otpBox} key={index}>
{otp[item]}
</Text>
))}
</View>
with styles of otpBoxesContainer and otpBox as below:
otpBoxesContainer: {
flexDirection: 'row'
},
otpBox: {
padding: 10,
marginRight: 10,
borderWidth: 1,
borderColor: lightGrey,
height: 45,
width: 45,
textAlign: 'center'
}
Now , since height of TextInput is set to 0, it doesn't show up to the user but it still takes the input. And we modify and store that input in such a way, that we can show it like values are entered in separate input boxes.
I was facing the same problem and I managed to develop a nicely working solution. Ignore provider, I am using it for my own purposes, just to setup form values.
Behavior:
User enters first pin number
Next input is focused
User deletes a number
Number is deleted
Previous input is focused
Code
// Dump function to print standard Input field. Mine is a little customised in
// this example, but it does not affects the logics
const CodeInput = ({name, reference, placeholder, ...props}) => (
<Input
keyboardType="number-pad"
maxLength={1}
name={name}
placeholder={placeholder}
reference={reference}
textAlign="center"
verificationCode
{...props}
/>
);
// Logics of jumping between inputs is here below. Ignore context providers it's for my own purpose.
const CodeInputGroup = ({pins}) => {
const {setFieldTouched, setFieldValue, response} = useContext(FormContext);
const references = useRef([]);
references.current = pins.map(
(ref, index) => (references.current[index] = createRef()),
);
useEffect(() => {
console.log(references.current);
references.current[0].current.focus();
}, []);
useEffect(() => {
response &&
response.status !== 200 &&
references.current[references.current.length - 1].current.focus();
}, [response]);
return pins.map((v, index) => (
<CodeInput
key={`code${index + 1}`}
name={`code${index + 1}`}
marginLeft={index !== 0 && `${moderateScale(24)}px`}
onChangeText={(val) => {
setFieldTouched(`code${index + 1}`, true, false);
setFieldValue(`code${index + 1}`, val);
console.log(typeof val);
index < 3 &&
val !== '' &&
references.current[index + 1].current.focus();
}}
onKeyPress={
index > 0 &&
(({nativeEvent}) => {
if (nativeEvent.key === 'Backspace') {
const input = references.current[index - 1].current;
input.focus();
}
})
}
placeholder={`${index + 1}`}
reference={references.current[index]}
/>
));
};
// Component caller
const CodeConfirmation = ({params, navigation, response, setResponse}) => {
return (
<FormContext.Provider
value={{
handleBlur,
handleSubmit,
isSubmitting,
response,
setFieldTouched,
setFieldValue,
values,
}}>
<CodeInputGroup pins={[1, 2, 3, 4]} />
</FormContext.Provider>
);
};
try this package https://github.com/Twotalltotems/react-native-otp-input
it works best with both the platforms
try this npm package >>> react-native OTP/Confirmation fields
below is the screenshot of the options available, yours fall under underline example.
below is the code for underline example.
import React, {useState} from 'react';
import {SafeAreaView, Text, View} from 'react-native';
import {
CodeField,
Cursor,
useBlurOnFulfill,
useClearByFocusCell,
} from 'react-native-confirmation-code-field';
const CELL_COUNT = 4;
const UnderlineExample = () => {
const [value, setValue] = useState('');
const ref = useBlurOnFulfill({value, cellCount: CELL_COUNT});
const [props, getCellOnLayoutHandler] = useClearByFocusCell({
value,
setValue,
});
return (
<SafeAreaView style={styles.root}>
<Text style={styles.title}>Underline example</Text>
<CodeField
ref={ref}
{...props}
value={value}
onChangeText={setValue}
cellCount={CELL_COUNT}
rootStyle={styles.codeFieldRoot}
keyboardType="number-pad"
textContentType="oneTimeCode"
renderCell={({index, symbol, isFocused}) => (
<View
// Make sure that you pass onLayout={getCellOnLayoutHandler(index)} prop to root component of "Cell"
onLayout={getCellOnLayoutHandler(index)}
key={index}
style={[styles.cellRoot, isFocused && styles.focusCell]}>
<Text style={styles.cellText}>
{symbol || (isFocused ? <Cursor /> : null)}
</Text>
</View>
)}
/>
</SafeAreaView>
);
};
const styles = StyleSheet.create({
root: {padding: 20, minHeight: 300},
title: {textAlign: 'center', fontSize: 30},
codeFieldRoot: {
marginTop: 20,
width: 280,
marginLeft: 'auto',
marginRight: 'auto',
},
cellRoot: {
width: 60,
height: 60,
justifyContent: 'center',
alignItems: 'center',
borderBottomColor: '#ccc',
borderBottomWidth: 1,
},
cellText: {
color: '#000',
fontSize: 36,
textAlign: 'center',
},
focusCell: {
borderBottomColor: '#007AFF',
borderBottomWidth: 2,
},
})
export default UnderlineExample;
source : Github Link to above Code
Hope it helps! :)
There is a plugin React Native Phone Verification works both with iOS and Android (Cross-platform) with this you can use verification code picker matching with your requirement.
We used to do it with single hidden input field as described in #Chethan’s answer. Now since RN already supports callback on back button on Android platform (since RN 0.58 or even before). It is possible to do this with just normal layout of a group of text inputs. But we also need to consider the text input suggestion on iOS or auto fill on Android. Actually, we have develop a library to handle this. Here is blog to introduce the library and how to use it. And the source code is here.
#kd12345 : You can do it here in:
onChangeText={(val) => {
setFieldTouched(`code${index + 1}`, true, false);
setFieldValue(`code${index + 1}`, val);
console.log(typeof val);
// LITTLE MODIFICATION HERE
if(index < 3 && val !== '') {
references.current[index + 1].current.focus();
// DO WHATEVER
}
}}