Can someone help with adding a text input to an alert in react native. Is it possible? I searched and found results that deals with multiple line text input which is not the case with me. Thanks in advance
This is possible. I believe this was only available initially for AlertIOS however it seems to have been intergrated to React Native Alert.
Edit: Although its added to Alert it does not seem to work for Android
Use
import { Alert } from 'react-native';
onButtonPress = () => {
Alert.prompt(
"Enter password",
"Enter your password to claim your $1.5B in lottery winnings",
[
{
text: "Cancel",
onPress: () => console.log("Cancel Pressed"),
style: "cancel"
},
{
text: "OK",
onPress: password => console.log("OK Pressed, password: " + password)
}
],
"secure-text"
);
};
}
More details: https://reactnative.dev/docs/alertios
Use react-native-dialog it works across platform and is simple enough.
There is no way you could add a text input to the Alert component according to the documentation, You will need to create a custom component by yourself in order to achieve that, example: use customise modal or use react-native-simple-dialogs
No way
Just use custom modal or third party library to achieve this...
Here is a spoiler using a custom Modal:
import React, { FC } from 'react'
import { View, TextInput, Modal, GestureResponderEvent } from 'react-native';
import { BoldText, IOSButton } from '..';
import { colors } from '../../constants';
import { customModalStyles, defaultStyles } from '../../styles';
interface Props {
modalVisible: boolean;
// onRequestClose,
textOne: string;
buttonOneTitle: string;
onPressOne: (event: GestureResponderEvent) => void;
value: string,
onChangeText: (text: string) => void;,
placeholder: string
}
const InputModal: FC<Props> = ({
modalVisible,
// onRequestClose,
textOne,
buttonOneTitle,
onPressOne,
value,
onChangeText,
placeholder
}) => {
return (
<Modal
animationType="fade"
transparent={true}
visible={modalVisible}
// onRequestClose={onRequestClose}
>
<View style={customModalStyles.centeredView}>
<View style={customModalStyles.modalView}>
<BoldText
style={customModalStyles.textSize}>{textOne}
</BoldText>
<TextInput
secureTextEntry
autoCapitalize="none"
style={[
defaultStyles.inputStyle,
defaultStyles.textInputShadow,
]}
value={value}
onChangeText={onChangeText}
placeholder={placeholder}
placeholderTextColor={colors.placeHolder}
/>
<IOSButton title={buttonOneTitle}
onPress={onPressOne}
/>
</View>
</View>
</Modal>
)
}
export default InputModal;
And the styles:
import { StyleSheet } from 'react-native';
import { colors } from '../constants';
import styleConstants from './styleConstants';
const customModalStyles = StyleSheet.create({
buttonsContainer: {
flexDirection: 'row',
justifyContent: 'space-between',
},
centeredView: {
flex: 1,
justifyContent: 'flex-end',
alignItems: 'center',
marginBottom: 20,
},
modalView: {
justifyContent: 'space-around',
alignItems: 'center',
width: styleConstants.width < 1000 ? 320 : 400,
height: styleConstants.height < 1000 ? 320 : 400,
backgroundColor: colors.primary,
borderRadius: 20,
paddingHorizontal: 15,
paddingTop: 10,
shadowColor: colors.secondary,
shadowOffset: {
width: 0,
height: 4,
},
shadowOpacity: 0.55,
shadowRadius: 8,
elevation: 20,
},
textSize: {
textAlign: 'center',
fontSize: styleConstants.fontSizeSM,
},
});
export default customModalStyles;
Then one may use it like this:
{modalVisible && (
<InputModal
modalVisible={modalVisible}
textOne={`...`}
buttonOneTitle="..."
onPressOne={async () => {
setModalVisible(false);
validatePasswordEtc()
}}
placeholder="..."
value={password}
onChangeText={handleChangePassword}
/>
)}
If you think for a moment you will find that an alert and a modal are both a kind of pop-up components. And that leads you to create your own pop-up component instead of using ready-made if you don't have it.
return (
<Modal
animationType="slide"
transparent={true}
onBackdropPress={() => console.log('Pressed')}
visible={props.modalVisible}
onRequestClose={ResetValues}>
<View
style={{
position: 'absolute',
bottom: 0,
right: 0,
backgroundColor: '#4D4D4D',
width: (windowWidth * 100) / 100,
height: (windowHeight * 100) / 100,
}}>
!!!!!!!!! your elements here like text,input,buttons and etc....!!!!!
</View>
</Modal>
);
Related
Im new in React, and Im starting with React native.
I'm working in my project, and in order to re-use code, I'm reading about HOC.
My use case is: I have a lot of views with a footer that have some buttons (one or two, it depends. They might have different actions, some of them navigates to another activity, other execute functions or state updates).
Im trying to execute a navigation.navigate from the "main" view, but I got an error: "Cant find variable: navigation".
This is my code:
index.js
import {
Text,
StyleSheet,
View,
TouchableOpacity,
ScrollView
} from 'react-native';
import withFooter from '../../components/withFooter';
const SignUp = ({ navigation }) => {
return (
<View style={styles.container}>
<View style={{ flex: 3 }}>
<Text>Test</Text>
</View>
</View>
)
};
export default withFooter(SignUp, {
buttons: [
{
text: 'Exit',
action: () => console.log('Exit'),
},
{
text: 'Accept',
action: () => navigation.navigate('PersonalDataSignUp'),
}
]
});
withFooter.js
import { Text, View, TouchableOpacity, StyleSheet } from 'react-native';
const withFooter = (WrappedComponent, { buttons }) => {
const WithFooter = props => {
return (
<>
<WrappedComponent {...props} />
<View style={{
flexDirection: 'row',
padding: 20
}}>
{
buttons.map(button => (
<TouchableOpacity style={[styles.button]} onPress={button.action}>
<Text style={{ fontWeight: '900' }}>{button.text}</Text>
</TouchableOpacity>
))
}
</View>
</>
)
};
return WithFooter;
};
const styles = StyleSheet.create({
button: {
flex: 1,
height: 50,
borderRadius: 5,
backgroundColor: '#FCCC00',
justifyContent: 'center',
alignItems: 'center',
borderColor: 'black',
borderWidth: 1
},
})
export default withFooter;
How can I send different functions from index in order to re use the footer code? Is there any other way to do it?. Thanks in advance!
react-native-multiselect-view is a seemingly simple library.....however I cant find a way to render a container as "pressed" (i.e. active) without physically pressing it. I want user to make choices and store them in Firebase.....this way when they re-open the app I will fetch choices from Firebase and update UI for their previous selections. Im guessing I need to change the data array data={['item1', 'item2']} to reflect whether item is active or not.....but cant figure out exactly how
import MultiSelectView from 'react-native-multiselect-view';
<MultiSelectView
data={['item1', 'item2']}
inactiveContainerStyle={{padding:2, borderColor: colorConsts.c3, opacity:0.25, borderWidth: 2, borderRadius:sizeConsts.widgetBorderRadius, backgroundColor:'white'}}
activeContainerStyle={{padding:2, borderColor: colorConsts.c2, borderWidth: 2, borderRadius:sizeConsts.widgetBorderRadius, backgroundColor:'white'}}
activeTextStyle={{color:colorConsts.c2}}
inactiveTextStyle={{color:colorConsts.c3}}
inactiveIcon={<IconFA name="plus-circle" size={20} style={{color:colorConsts.c3}}/>}
activeIcon={<IconFA name="minus-circle" size={20} style={{color:colorConsts.c2}}/>}
onSelectionStatusChange={this._onSelectionStatusChange}
/>
I emailed author who kindly came right back to me with: "It's not implemented at the moment. But here the code screenshot where it can be handled. U can raise a PR or fork repo and do it. If u need me to do it for u let me know.."
I decided instead to write my own and use much of the authors code (below)....I pull user preferences from Firebase....into Redux....into local state
/* eslint-disable react-native/no-inline-styles */
import React, {Component} from 'react';
import {View, TouchableOpacity, Text, StyleSheet} from 'react-native';
import {sizeConsts, colorConsts} from '../globalConst';
import IconFA from 'react-native-vector-icons/FontAwesome';
import database from '#react-native-firebase/database';
import {connect} from 'react-redux';
//*********************************************************
//REDUX
//*********************************************************
const passStateToProps = state => ({
arrObjInterests: state.profile.mySelf.arrInterests,
userId: state.auth.userId,
});
class MyMultiSelectView extends Component {
//To update the status of any arrInterests object, run a map on the array and check for some unique value of each object,
//in case of condition=true, return the new object with updated value, else same object.
_onPress = (label, oldValue) => {
//FIRST UPDATE STATE FOR QUICK UI RESPONSE
this.setState(
prevState => ({
//map through prevState interests array and return each object same as you saw it
//.....unless its a match in which case you update the value
arrInterests: prevState.arrInterests.map(obj =>
obj.label === label ? {...obj, value: !oldValue} : obj,
),
}),
() => {
//NEXT UPDATE FBS WITH UPDATED STATE
//prettier-ignore
database().ref('/profiles/users/' + this.props.userId + '/arrInterests').set(this.state.arrInterests);
},
);
};
constructor(props) {
super(props);
//populate state with interests array arrInterests from Redux arrObjInterests
this.state = {
arrInterests: this.props.arrObjInterests,
};
}
render() {
return (
//prettier-ignore
<View
style={{flexDirection: 'row', flexWrap: 'wrap', flex: 1, padding: 15}}>
{/* map through interests array and create TouchableOpacity for each */}
{this.state.arrInterests.map((item, index) => (
<TouchableOpacity key={index} onPress={() => {
this._onPress(item.label, item.value);
}}>
<View style={!item.value ? [styles.container, {borderColor: colorConsts.c3, opacity:0.25}] : [styles.container, {borderColor: colorConsts.c2}]}>
<Text style={!item.value ? [styles.text, {color:colorConsts.c3}] : [styles.text, {color:colorConsts.c2}]}>{item.label}</Text>
{!item.value ? <IconFA name="plus-circle" size={20} style={[styles.icon, {color:colorConsts.c3}]}/> : <IconFA name="minus-circle" size={20} style={[styles.icon, {color:colorConsts.c2}]}/>}
</View>
</TouchableOpacity>
))}
</View>
);
}
}
const styles = StyleSheet.create({
container: {
paddingVertical: 5,
marginTop: 10,
marginRight: 10,
paddingLeft: 10,
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
borderWidth: 2,
borderRadius: sizeConsts.widgetBorderRadius,
backgroundColor: '#f8f8f8',
},
text: {
fontSize: 16,
color: 'black',
marginRight: 6,
},
icon: {
width: 20,
marginRight: 2,
},
});
export default connect(
passStateToProps,
null,
)(MyMultiSelectView);
im pretty new to react-native, and facing right now with that problem:
google-places-autocomplete not working when wrapping it within a view (the scrolldown doesn't display), when i'm deleting the view it works..
console log doesn't display anything, the android emulator else.
help some1 ? thanks anyway :)
import React,{useState} from 'react';
import FormCity from '../components/FormCity';
import {View,Text} from 'react-native';
const addFlightScreen = () => {
const [destination,setDest] = useState("");
return (
<View>
<FormCity onDest={setDest}/>
<Text>Hello</Text>
</View>
);
};
export default addFlightScreen;
and
import React from 'react'; import { GooglePlacesAutocomplete } from 'react-native-google-places-autocomplete';
const FormCity = ({onDest}) => {
return (
<GooglePlacesAutocomplete
placeholder='Enter Country, City (your destination)'
minLength={2}
autoFocus={false}
returnKeyType={'default'}
keyboardAppearance={'light'}
listViewDisplayed='auto'
fetchDetails={true}
renderDescription={row => row.description}
onPress={(data, details = null) => {
onDest(data.description);
console.log(data.description);
}}
getDefaultValue={ () => ''}
query={{
key: 'API_KEY',
language: 'en',
types: '(cities)'
}}
styles={{
textInputContainer: {
width: '100%'
},
description: {
fontWeight: 'bold'
},
predefinedPlacesDescription: {
color: '#1faadb'
}
}}
/> ); };
export default FormCity;
It depends on your use case. But I faced this issue and my solution was to put Places component in main view of page (flex:1 OR full height width) as its last child and make its position absolute. Because when you type in search, its height need to be increased dynamically. Try giving styles to your parent view.
In your parent View add flexGrow: 1
return (
<View style={{ flexGrow: 1 }}>
<FormCity onDest={setDest}/>
<Text>Hello</Text>
</View>
);
I had the same problem.
I wrapped the autocomplete component in a view with an absolute position and a height like below :
<View style={styles.autoComplete}>
<GooglePlacesAutocomplete
placeholder="Type a place"
query={{key: config.GOOGLE_API_KEY}}
/>
</View>
<View style={styles.mapPreView}>{locationPreview}</View>
style :
const styles = StyleSheet.create({
autoComplete: {
marginTop: 20,
height: 200,
width: '100%',
position: 'absolute',
zIndex: 100,
},
mapPreView: {
width: '100%',
height: 200,
marginTop: 80,
justifyContent: 'center',
alignItems: 'center',
borderRadius: 4,
},
});
My page has only a single TextInput, and I have passed in the autoFocus prop: autoFocus: true.
<TextInput
style={styles.textInput}
placeholder="Quiz Deck Title"
autoFocus={true}
value={this.state.title}
onChangeText={(title) => this.controlledTextInput(title)}
/>
What I am trying to avoid is requiring the user to "click" in the TextInput box before the keyboard pops up.
But I would also like to have the soft keyboard also open automatically (if the device does not have a hardware keyboard).
Is there way to make this happen in react native? I am developing for both ios and android.
If it matters, my page is navigated to via TabNavigator.
I mention this because a comment to another similar SO question (see below) suggests they had a similar issue when they arrived at their page using StackNavigator.
Note on Similar SO questions:
How to open keyboard automatically in React Native?
: does not provide a solution, and comments by others suggest the same results as myself: input is focused, but keyboard does not automatically open.
Close/hide the Android Soft Keyboard
and Android: show soft keyboard automatically when focus is on an EditText
: are using native android code (java), not react native code (javascript).
Note: I am developing using the android emulator Nexus 6P with android 23 (as recommended), and ios simulator with iPhone 6s, as I do not have physical devices.
Edit: Adding Requested Code
NewDeck.js (the view I want the keyboard to auto pop up on):
import React from 'react';
import { connect } from 'react-redux';
import { View, Text, TouchableOpacity,
TextInput, KeyboardAvoidingView,
StyleSheet, Platform,
} from 'react-native';
import StyledButton from '../components/StyledButton';
import { saveDeck } from '../store/decks/actionCreators';
import { getDeckList } from '../store/decks/selectors';
import { fetchDecks } from '../utils/api';
import { saveDeckTitle } from '../utils/api';
} from '../utils/colors';
import { titleCase, stripInvalidChars, makeStringUnique }
from '../utils/helpers';
import { white, gray, primaryColor, primaryColorDark, primaryColorLight,
class NewDeck extends React.Component {
state = {
title: '',
canSubmit: false,
}
componentDidMount () {
this.textInputRef.focus()
}
controlledTextInput(title){
title = titleCase(stripInvalidChars(title));
const canSubmit = this.isValidInput(title);
this.setState({ title, canSubmit });
}
isValidInput(text){
return text.trim() !== '';
}
onBlur(){
title = this.state.title.trim();
const unique = makeStringUnique(title, this.props.existingTitles);
this.setState({ title: unique });
}
onSubmit(){
let title = this.state.title.trim();
title = makeStringUnique(title, this.props.existingTitles)
saveDeckTitle(title)
this.props.navigation.navigate('Home');
}
render() {
return (
<View style={styles.container}>
<View style={[styles.cardContainer, {flex: 1}]}>
<Text style={styles.instructionsText}
>
Title for your New Quiz Deck
</Text>
<KeyboardAvoidingView {...keyboardAvoidingViewProps}>
<TextInput
style={styles.textInput}
placeholder="Quiz Deck Title"
value={this.state.title}
onChangeText={(title) => this.controlledTextInput(title)}
/* autoFocus={true} */
ref={ref => this.textInputRef = ref}
/>
</KeyboardAvoidingView>
</View>
<KeyboardAvoidingView
{...keyboardAvoidingViewProps}
style={[styles.buttonsContainer, styles.buttonContainer]}
>
<StyledButton
style={[styles.item, style={flex: 2}]}
onPress={() => this.onSubmit()}
disabled={!this.state.canSubmit}
>
<Text>
Submit
</Text>
</StyledButton>
</KeyboardAvoidingView>
</View>
);
}
}
const keyboardAvoidingViewProps = {
behavior: 'padding',
};
// ...styles definition here, - I posted it in a later code block, to minimize
// clutter, in the event that it is irrelevant to this issue
function mapStoreToProps(store){
const decks = getDeckList(store) || null;
// ensure titles are unique (better UX than if just make id unique)
const existingTitles = decks && decks.map(deck => {
return deck.title
}) || [];
return {
existingTitles,
}
}
export default connect(mapStoreToProps)(NewDeck);
TabNavigator and StackNavigator code (in App.js):
// ... the TabNavigator I'm using:
import { TabNavigator, StackNavigator } from 'react-navigation';
//... the class's render method, uses StackNavigator (as MainNavigation)
render(){
return (
<Provider store={createStore(rootReducer)}>
<View style={{flex:1}}>
<AppStatusBar
backgroundColor={primaryColor}
barStyle="light-content"
/>
<MainNavigation />
</View>
</Provider>
);
}
}
// ...
const Tabs = TabNavigator(
{
DeckList: {
screen: DeckList,
navigationOptions: {
tabBarLabel: 'Quiz Decks',
tabBarIcon: ({ tintColor }) => // icons only show in ios
<Ionicons name='ios-bookmarks' size={30} color={tintColor} />
},
},
NewDeck: {
screen: NewDeck,
navigationOptions: {
tabBarLabel: 'Create New Deck',
tabBarIcon: ({ tintColor }) => // icons only show in ios
<FontAwesome name='plus-square' size={30} color={tintColor} />
},
},
},
{
navigationOptions: {
// do-not-display page headers for Tab Navigation
header: null
},
tabBarOptions: {
// ios icon and text color; android text color
activeTintColor: Platform.OS === 'ios' ? primaryColor : white,
pressColor: white,
indicatorStyle: {
backgroundColor: primaryColorDark,
height: 3,
},
style: {
height: 56,
backgroundColor: Platform.OS === 'ios' ? white : primaryColor,
shadowColor: 'rgba(0, 0, 0, 0.24)',
shadowOffset: {
width: 0,
height: 3
},
shadowRadius: 6,
shadowOpacity: 1
}
}
}
);
//... StackNavigator uses TabNavigator (as Tabs)
const stackScreenNavigationOptions = {
headerTintColor: white,
headerStyle: {
backgroundColor: primaryColor,
}
};
const MainNavigation = StackNavigator(
// RouteConfigs: This is analogous to defining Routes in a web app
{
Home: {
screen: Tabs, // Which also loads the first Tab (DeckList)
},
Deck: {
screen: Deck,
navigationOptions: stackScreenNavigationOptions,
},
Quiz: {
screen: Quiz,
navigationOptions: stackScreenNavigationOptions,
},
NewDeck: {
screen: NewDeck,
navigationOptions: stackScreenNavigationOptions,
},
NewCard: {
screen: NewCard,
navigationOptions: stackScreenNavigationOptions,
},
},
);
This is the styles definition for NewDeck.js
const styles = StyleSheet.create({
// CONTAINER styles
wrapper: {
// this was the previous container style
flex: 1,
backgroundColor: white,
alignItems: 'center',
justifyContent: 'center',
},
container: {
flex: 1,
backgroundColor: white,
alignItems: 'center',
justifyContent: 'space-between',
padding: 10,
paddingTop: 30,
paddingBottom: 5,
},
cardContainer: {
flex: 1,
justifyContent: 'flex-start',
alignSelf: 'stretch',
backgroundColor: '#fefefe',
padding: 20,
marginLeft: 30,
marginRight: 30,
marginTop: 10,
borderRadius: Platform.OS === 'ios' ? 20 : 10,
shadowRadius: 3,
shadowOpacity: 0.8,
shadowColor: 'rgba(0, 0, 0, 0.24)',
shadowOffset: {
width: 0,
height: 3,
},
marginBottom:20,
},
buttonsContainer: {
flex: 3,
alignSelf: 'stretch',
justifyContent: 'flex-start',
},
buttonContainer: {
justifyContent: 'center',
margin: 10,
},
// TEXT Styles
instructionsText: {
flex: 1,
fontSize: 20,
color: gray,
alignSelf: 'center',
textAlign: 'center',
},
// INPUTTEXT styles
textInput: {
fontSize: 27,
color: primaryColor,
alignSelf: 'stretch',
flexWrap: 'wrap',
textAlign: 'center',
marginTop: 10,
},
});
StyledButton.js (Basically, TouchableOpacity with platform-specific styling, for universal use across the app):
import React from 'react';
import { Text, TouchableOpacity, StyleSheet, Platform } from 'react-native';
import { white, gray, primaryColor, primaryColorLight, primaryColorDark} from '../utils/colors';
export default function TextButton({ children, onPress, customColor, disabled=false }) {
const disabledColor = disabled ? gray : null;
const backgroundColor = Platform.OS==='ios' ? white : disabledColor || customColor || primaryColorLight;
const borderColor = Platform.OS==='ios' ? disabledColor || customColor || primaryColorDark
const textColor = Platform.OS==='ios' ? disabledColor || customColor || primaryColor : white;
const btnStyle = Platform.OS==='ios' ? styles.iosBtn : styles.androidBtn;
const txtStyle = styles.txtDefault;
const btnColor = { backgroundColor, borderColor };
const txtColor = { color: textColor };
return (
<TouchableOpacity
onPress={onPress}
disabled={disabled}
style={[btnStyle, btnColor]}
>
<Text
style={[styles.txtDefault, txtColor]}
>
{children}
</Text>
</TouchableOpacity>
);
}
const styles = StyleSheet.create({
txtDefault: {
textAlign: 'center',
// because of bleeding of white text to colored background on android,
// enlarge text (or increase fontWeight) for better readability
fontSize: Platform.OS==='ios' ? 15 : 18,
padding: 10,
},
iosBtn: {
height: 45,
borderRadius: 7,
alignSelf: 'center',
justifyContent: 'center',
alignItems: 'center',
// ios only settings
borderColor: primaryColorDark,
borderWidth: 1,
borderRadius: 3,
paddingLeft: 25,
paddingRight: 25,
},
androidBtn: {
height: 45,
borderRadius: 5,
alignSelf: 'center',
justifyContent: 'center',
alignItems: 'center',
// android- only settings
// (padding accepts clicks, vs. margin === no-click zone)
padding: 20,
paddingLeft: 15,
paddingRight: 15,
},
});
// ios has white buttons with colored outlines and colored text
// android has colored buttons with white text
// Pass in a button color, or it defaults to the App's primary colors
just wrap the ref inside timeout
setTimeout(()=>{this.textInputRef.focus()},100)
Old Issue, but if anyone is searching through here for an answer..
It looks like the keyboard doesn't come up if you're stuck in an animation (e.g. if coming from another screen).
You can use a ref to focus on your input, but must wrap in within InteractionManager.runAfterInteractions for it to work correctly.
This is how I solved it:
export const CustomInput: FunctionComponent<Props & TextInputProps> = ({
error,
...props
}) => {
const inputRef = useRef<TextInput>(null);
useEffect(() => {
// Must run after animations for keyboard to automatically open
InteractionManager.runAfterInteractions(() => {
if (inputRef?.current) {
inputRef.current.focus();
}
});
}, [inputRef]);
return (
<View>
<TextInput
ref={inputRef}
{...props}
/>
{error && <ErrorText>{error}</ErrorText>}
</View>
);
};```
You can always use .focus on any of your TextInput when the component loads, to show the keyboard if you want to avoid the autoFocus.
componentDidMount () {
this.textInputRef.focus()
}
<TextInput
style={styles.textInput}
ref={ref => this.textInputRef = ref}
placeholder="Quiz Deck Title"
autoFocus={true}
value={this.state.title}
onChangeText={(title) => this.controlledTextInput(title)}
/>
As mentioned in the docs
Two methods exposed via the native element are .focus() and .blur() that will focus or blur the TextInput programmatically.
I only needed the 'autoFocus' prop to get this going as at today.
https://reactnative.dev/docs/textinput#autofocus
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
}
}}