how to design react native OTP enter screen? - react-native

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
}
}}

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.

FlatList show according to TextInput handle

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.

Getting Undefined is not a function in one of the function

I am very new to React Native. I am trying to make autocomplete text box in React Native. I am using react-native-autocomplete-input plugin. I am reading the data from JSON file. I keep getting this error. user is entering the service name in the text box so they can enter ser and it will show service1 as an option for user to select.
Below is my App.js code:
/**
* Sample React Native App
* https://github.com/facebook/react-native
* #flow
*/
import service from './services.json';
import Autocomplete from 'react-native-autocomplete-input';
import React, { Component } from 'react';
import {
StyleSheet,
Text,
TouchableOpacity,
View
} from 'react-native';
class Autocomp extends Component {
static renderServices(coservice) {
const { ser, Location, secondLoc} = coservice;
return (
<View>
<Text style={styles.titleText}>{ser}</Text>
<Text style={styles.openingText}>{secondLoc}</Text>
</View>
);
}
constructor(props) {
super(props);
this.state = {
query: '',
services:[]
};
}
componentDidMount(){
const {results: services} = service;
this.setState({services});
}
findServices(query) {
if (query === '') {
return [];
}
const {services } = this.state;
const regex = new RegExp(`${query.trim()}`, 'i');
return services.filter(coservice=> coservice.ser.search(regex) >= 0);
}
render() {
const { query } = this.state;
const services = this.findservices(query);
const comp = (a, b) => a.toLowerCase().trim() === b.toLowerCase().trim();
return (
<View style={styles.container}>
<Autocomplete
autoCapitalize="none"
autoCorrect={false}
containerStyle={styles.autocompleteContainer}
data={services.length === 1 && comp(query, services[0].ser) ? [] : services}
defaultValue={query}
onChangeText={text => this.setState({ query: text })}
placeholder="Enter Services here"
renderItem={({ ser, Phone }) => (
<TouchableOpacity onPress={() => this.setState({ query: ser })}>
<Text style={styles.itemText}>
{ser}
</Text>
</TouchableOpacity>
)}
/>
<View style={styles.descriptionContainer}>
{services.length > 0 ? (
Autocomp.renderServices(services[0])
) : (
<Text style={styles.infoText}>
Enter services
</Text>
)}
</View>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
backgroundColor: '#F5FCFF',
flex: 1,
paddingTop: 25
},
autocompleteContainer: {
flex: 1,
left: 0,
position: 'absolute',
right: 0,
top: 0,
zIndex: 1
},
itemText: {
fontSize: 15,
margin: 2
},
descriptionContainer: {
// `backgroundColor` needs to be set otherwise the
// autocomplete input will disappear on text input.
backgroundColor: '#F5FCFF',
marginTop: 25
},
infoText: {
textAlign: 'center'
},
titleText: {
fontSize: 18,
fontWeight: '500',
marginBottom: 10,
marginTop: 10,
textAlign: 'center'
},
directorText: {
color: 'grey',
fontSize: 12,
marginBottom: 10,
textAlign: 'center'
},
openingText: {
textAlign: 'center'
}
});
export default Autocomp;
below is my JSON (services.json) file:
{
"id":1,
"ser": "Service1",
"Location": "TestLoc1",
"Phone":"(999)-921-9292",
"SecondLoc": "TestLoc",
"email":"accrmail#asrclkrec.com",
"sourceLat":"33.977806",
"sourceLong":"-117.373261",
"destLatL1":"33.613355",
"destLongL1":"-114.596569",
"destLatL2":"33.761693",
"destLongL2":"-116.971169",
"destAddr1": "Test Drive, 99999",
"destAddr2": "Test City, Test Drive, 92345"
},
{
"id":1,
"ser": "TestService",
"Location": "TestLoc1",
"Phone":"(999)-921-9292",
"SecondLoc": "TestLoc",
"email":"accrmail#asrclkrec.com",
"sourceLat":"33.977806",
"sourceLong":"-117.373261",
"destLatL1":"33.613355",
"destLongL1":"-114.596569",
"destLatL2":"33.761693",
"destLongL2":"-116.971169",
"destAddr1": "Test Drive, 99999",
"destAddr2": "Test City, Test Drive, 92345"
},
]
Any help will be highly appreciated.
This is just a silly mistake, your function names don't match. You called findservices, but it should be findServices, with an uppercase S
Also, I want to point out that your way of finding suggestions is fine, but there's a bug in it. In your findServices function, you have
const regex = new RegExp(`${query.trim()}`, 'i');
You're constructing a new regular expression from query, but this doesn't always succeed. For example, if user enters a special character such as [, then this line of code will throw an error, because query now contains an open bracket but not a closing bracket ], therefore, a regex cannot be constructed from query. You should change it to something like this:
findServices(query) {
const inputValue = query.trim().toLowerCase();
const inputLength = inputValue.length;
const { services } = this.state;
return inputLength === 0 ? [] : services.filter(ser =>
ser.toLowerCase().slice(0, inputLength) === inputValue);
}
In addition of K.Wu answer, please remove this line const services = this.findservices(query); in render method and placed it inside onChangeText of autocomplete . It should be triggered after user has typing the text on autocomplete box not rendered by default
Autocomplete
autoCapitalize="none"
autoCorrect={false}
containerStyle={styles.autocompleteContainer}
data={services.length === 1 && comp(query, services[0].ser) ? [] : services}
defaultValue={query}
onChangeText={text => this.findServices(text}
placeholder="Enter Services here"
renderItem={({ ser, Phone }) => (
<TouchableOpacity onPress={() => this.setState({ query: ser })}>
<Text style={styles.itemText}>
{ser}
</Text>
</TouchableOpacity>
)}
/>

Type Error in filter function

I am trying to make a autocomplete text box in React Native. I am getting an error in filter function. When the user types the services then the text box should get autocompleted with the full name of the service.The service name is coming from my json file. I am using 'react-native-autocomplete-input' in order to accomplish this. Below is the screen shot of the error:
Below is my App.js code.
/**
* Sample React Native App
* https://github.com/facebook/react-native
* #flow
*/
import service from './services.json';
import Autocomplete from 'react-native-autocomplete-input';
import React, { Component } from 'react';
import {
StyleSheet,
Text,
TouchableOpacity,
View
} from 'react-native';
class Autocomp extends Component {
static renderServices(coservice) {
const { ser, Location, secondLoc} = coservice;
return (
<View>
<Text style={styles.titleText}>{ser}</Text>
<Text style={styles.openingText}>{secondLoc}</Text>
</View>
);
}
constructor(props) {
super(props);
this.state = {
query: '',
services:[]
};
}
componentDidMount(){
const {results: services} = service;
this.setState({services});
}
findServices(query) {
const inputValue = query.trim().toLowerCase();
const inputLength = inputValue.length;
const { services } = this.state;
return inputLength === 0 ? [] : services.filter(ser =>ser.toLowerCase().slice(0, inputLength) === inputValue);
}
render() {
const { query } = this.state;
const services = this.findServices(query);
const comp = (a, b) => a.toLowerCase().trim() === b.toLowerCase().trim();
return (
<View style={styles.container}>
<Autocomplete
autoCapitalize="none"
autoCorrect={false}
containerStyle={styles.autocompleteContainer}
data={services.length === 1 && comp(query, services[0].ser) ? [] : services}
defaultValue={query}
onChangeText={text => this.setState({ query: text })}
placeholder="Enter Services here"
renderItem={({ ser, Phone }) => (
<TouchableOpacity onPress={() => this.setState({ query: ser })}>
<Text style={styles.itemText}>
{ser}
</Text>
</TouchableOpacity>
)}
/>
<View style={styles.descriptionContainer}>
{services.length > 0 ? (
Autocomp.renderServices(services[0])
) : (
<Text style={styles.infoText}>
Enter services
</Text>
)}
</View>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
backgroundColor: '#F5FCFF',
flex: 1,
paddingTop: 25
},
autocompleteContainer: {
flex: 1,
left: 0,
position: 'absolute',
right: 0,
top: 0,
zIndex: 1
},
itemText: {
fontSize: 15,
margin: 2
},
descriptionContainer: {
// `backgroundColor` needs to be set otherwise the
// autocomplete input will disappear on text input.
backgroundColor: '#F5FCFF',
marginTop: 25
},
infoText: {
textAlign: 'center'
},
titleText: {
fontSize: 18,
fontWeight: '500',
marginBottom: 10,
marginTop: 10,
textAlign: 'center'
},
directorText: {
color: 'grey',
fontSize: 12,
marginBottom: 10,
textAlign: 'center'
},
openingText: {
textAlign: 'center'
}
});
export default Autocomp;
Below is my services.json file
{
"id":1,
"ser": "Service1",
"Location": "TestLoc1",
"Phone":"(999)-921-9292",
"SecondLoc": "TestLoc",
"email":"accrmail#test.com",
"sourceLat":"33.977806",
"sourceLong":"-117.373261",
"destLatL1":"33.613355",
"destLongL1":"-114.596569",
"destLatL2":"33.761693",
"destLongL2":"-116.971169",
"destAddr1": "Test Drive, 99999",
"destAddr2": "Test City, Test Drive, 92345"
},
{
"id":1,
"ser": "TestService",
"Location": "TestLoc1",
"Phone":"(999)-921-9292",
"SecondLoc": "TestLoc",
"email":"accrmail#test.com",
"sourceLat":"33.977806",
"sourceLong":"-117.373261",
"destLatL1":"33.613355",
"destLongL1":"-114.596569",
"destLatL2":"33.761693",
"destLongL2":"-116.971169",
"destAddr1": "Test Drive, 99999",
"destAddr2": "Test City, Test Drive, 92345"
},
]
any help will be highly appreciated. I checked the function. everything looks correct.
Assuming that your json file is as shown here, there are two problems with your code.
Destructuring is wrong. Since you're directly importing an object from a json file as name services, which has not been assigned to any named constant / variable, therefore it cant be destructured.
Therefore you must change the code as
import services from './services.json';
componentDidMount(){
this.setState({services});
}
You're trying to convert a service object toLowerCase here
ser =>ser.toLowerCase()
which needs to be changed to
services.filter(({ser}) => ser.toLowerCase().slice(0, inputLength) === inputValue);

How to add icon in each tab in custom Tabs?

I am using react-navigation .
I want to add icon for the tab.
CustomTabs.js from example directory
if you are to use react-native-vector-icon is much easier, just create an array like the one i created below, for all the names of the icon you want to use and if you want to use image, then you will have to use image links because the last time i checked react native won't allow you to load static assets dynamically.
Benefit of using an icon especially react-native-vector-icon:
Access to tonnes of iconsets.
Styling based on if its focused or not.
....and others things i can't remember.
`
.....
import Icon from 'react-native-vector-icons/Ionicons';
const styles = {
body: {
backgroundColor: '#3b4147',
height: 60,
},
tabWrapper: {
flexDirection: 'row',
justifyContent: 'center',
alignItems: 'center',
height: 50,
},
tabInnerWrapper: {
marginRight: 12,
marginLeft: 12,
justifyContent: 'center',
alignItems: 'center',
},
textStyle: {
fontSize: 12,
color: '#62666b',
},
focusTextStyle: {
fontSize: 12,
color: '#acafb1',
},
};
const {body, tabWrapper, tabInnerWrapper, textStyle, focusTextStyle} = styles;
const focusIconColor = '#acafb1';
const iconColor = '#62666b';
const IconNames = ['ios-compass-outline', 'ios-cut-outline', 'ios-chatboxes-outline'];
const IconNamesFocus = ['ios-compass', 'ios-cut', 'ios-chatboxes'];
const CustomTabBar = ({ navigation: { state, navigate }}) => {
const { routes } = state;
return (
<View style={body}>
<View style={tabWrapper}>
{routes && routes.map((route, index) => {
const focused = index === state.index;
return (
<TouchableOpacity
key={route.key}
onPress={() => navigate(route.routeName)}
style={tabInnerWrapper}
>
<Icon
name={focused ? IconNamesFocus[index] : IconNames[index]}
size={25}
color={focused ? focusIconColor : iconColor}
/>
<Text style={focused ? focusTextStyle : textStyle}>
{route.routeName}
</Text>
</TouchableOpacity>
);
})}
</View>
</View>
);
};
`