Control word/text break on React Native <Text> element - react-native

Is there a way to control the word-break behaviour for text within a Text component? With a multiple-line Text component on iOS it automatically breaks the text where there are word breaks (I haven't checked android yet, but I'd need it to work there too). I was hoping to change that behaviour to break on the per-character level, like you can do this CSS rule:
word-break: break-all;

transform <text>str</text> to <View><text>s</text><text>t</text><text>r</text></View> by using split & map!
export default class PageCard extends React.Component {
constructor(props) {
super(props);
this.state = {
tail: false,
longWord: 'longWord longWord bbbbbbbbbbbbbbbbbbbbb longWord longWord longWord',
longWordTail: ' longWord longWord longWord bbbbbbbbbbbbbbbbbbbbb longWord longWord longWord',
longWordTail2: ' longWord longWord bbbbbbbbbbbbbbbbbbbbb longWord longWord longWord',
};
}
render() {
return (
<ScrollView style={{flex: 1, paddingTop: 60, paddingHorizontal: 10}}>
<View style={Styles.breakWordWrap}>
<Text numberOfLines={2} ellipsizeMode="tail" style={Styles.breakWord}>
{this.state.longWord}
</Text>
</View>
<View style={Styles.breakWordWrap}>
{
this.breakWord(this.state.longWord)
}
</View>
<View style={[Styles.breakWordWrap, Styles.breakWordWrapTail]}>
{
this.breakWord(this.state.longWordTail)
}
</View>
<View style={[Styles.breakWordWrap, Styles.breakWordWrapTail]}>
{
this.breakWord(this.state.longWordTail, true)
}
</View>
{/*<View style={[Styles.breakWordWrap, Styles.breakWordWrapTail]}>*/}
{/* {*/}
{/* this.breakWord(this.state.longWordTail2, true)*/}
{/* }*/}
{/*</View>*/}
</ScrollView>
)
}
breakWord = (str = '', tail = false) => {
let strArr = (tail ? str + ' ' : str).split('');
return strArr.map((item, index) => tail && strArr.length === index + 1 ?
<Text key={item + index} style={[Styles.breakWord, Styles.breakWordTail, !this.state.tail && Styles.breakWordTailHide]}>...</Text> :
(tail && strArr.length === index + 2 ? <Text key={item + index} style={[Styles.breakWord]} onLayout={this.breakWordLast}>{item}</Text>
: <Text key={item + index} style={[Styles.breakWord]}>{item}</Text>)
);
}
breakWordLast = (e) => {
console.log(e.nativeEvent.layout)
if (e.nativeEvent.layout.y > 50) {
this.setState({
tail: true
})
}
}
}
const Styles = {
box: {
marginTop: 10,
},
title: {
fontWeight: 'bold',
color: '#333',
textAlign: 'center'
},
breakWordWrap: {
flexWrap: 'wrap',
flexDirection: 'row',
// not
borderWidth: 1,
marginTop: 30,
},
breakWordWrapTail: {
position: 'relative',
height: 50,
overflow: 'hidden'
},
breakWord: {
lineHeight: 25
},
breakWordTail: {
position: 'absolute',
backgroundColor: '#fff',
right: 0,
bottom: 0,
height: 25
},
breakWordTailHide: {
opacity: 0
}
}

There is an Android-only textbreakstrategy property for Text components that allows some control on how a text should be split. The property values map to Android's native android:breakStrategy values.
This property does not seem to translate to iOS though.
Otherwise, if you have preknowledge about the Text value, you could indicate text split with a Soft Hyphen
<Text>aaaaaaaaaaaaaaaaaa bbbbbbbbbbb­CCCCC.</Text>
This would render as:
aaaaaaaaaaaaaaaaaa bbbbbbbbbbb-
CCCCC.
If neither suffice, I suppose this is beyond the capabilities of a Text component and you may have to revert to another component. Maybe one that renders simple html.

Try add flexWrap: "wrap" to the style of the Text component
Example:
Code
<View style={styles.container}>
<Text style={styles.text}>{text}</Text>
</View>
Style
export default StyleSheet.create({
container: {
flexDirection: 'row',
alignItems: 'center',
},
text: {
width: 0,
flexGrow: 1,
flex: 1,
}
});

There is the possibility with following code
<Text style={{ flexShrink: 1 }}>

Related

In React Native how to create UI same as attached image?

I have to create user interface same as attached image. I have to wrap the tabs according to size of text which is dynamic(may be of different size). How to do this in React Native Screenshot of UI
You can use react-native-selectmultiple-button module if you are using UI to select many things.
You can run npm install react-native-selectmultiple-button --save
Example
import {
SelectMultipleButton,
SelectMultipleGroupButton
} from "react-native-selectmultiple-button";
<SelectMultipleButton
buttonViewStyle={{
borderRadius: 10,
height: 40
}}
textStyle={{
fontSize: 15
}}
highLightStyle={{
borderColor: "gray",
backgroundColor: "transparent",
textColor: "gray",
borderTintColor: ios_blue,
backgroundTintColor: ios_blue,
textTintColor: "white"
}}
multiple={true}
value={interest}
selected={this.state.multipleSelectedData.includes(interest)}
singleTap={valueTap => this._singleTapMultipleSelectedButtons(interest)}
/>;
<SelectMultipleGroupButton
containerViewStyle={{
justifyContent: "flex-start"
}}
highLightStyle={{
borderColor: "gray",
backgroundColor: "transparent",
textColor: "gray",
borderTintColor: ios_blue,
backgroundTintColor: "transparent",
textTintColor: ios_blue
}}
onSelectedValuesChange={selectedValues =>
this._groupButtonOnSelectedValuesChange(selectedValues)
}
group={multipleGroupData}
/>;
hi #shivam tiwari this can be acheived in many ways. in that one of the best method is using .map function with parent view styles of flexdirection : row and flexWrap: 'wrap'
also take a boolean value selected for each item in the array initially to false and update the particular item to true or false based on selection.
Please check below example:
let industries = this.state.industries
for (let i = 0; i < industries.length; i++) {
industries[i].selected = false
}
this.setState({industries: industries})
<View style={{ flexDirection: 'row', flexWrap: 'wrap', margin: 10, marginTop: 5 }}>
{this.renderIndustry(this.state.preferredIndustry)}
and in renderIndustry method you can do .map functionality like
//RENDER INDUSTRY
renderIndustry(data) {
if (data.length === 0) {
return null
}
return data.map((item, index) => {
return (
<CardView
key={item.industryname}
style={[styles.cardView, { backgroundColor: item.selected ? '#4291E2' : '#F9FAFB', borderWidth: 1, borderColor: item.selected ? '#4291E2' : '#E0E8F1' }]}
cardElevation={item.selected ? 3 : 0}
cardMaxElevation={5}
cornerRadius={10}
cornerOverlap={false}
>
<TouchableWithoutFeedback onPress={() => { this.selectIndustry(item, index) }}>
<View style={{
alignItems: 'center',
justifyContent: 'center',
height: '100%',
width: '100%',
backgroundColor: '#0000'
}}>
<Text style={{ fontSize: 14, color: item.selected ? '#fff' : '#000', margin: 15 }}>{item.industryname}</Text>
</View>
</TouchableWithoutFeedback>
</CardView>
);
});
}
and in onPress change the value of selected to true or false based on the previous value like....
//SELECT INDUSTRY
selectIndustry = (item, index) => {
let industries = this.state.industries
for (let i = 1; i <= industries.length; i++) {
if (industries[i - 1].industryid === item.industryid) {
industries[i - 1].selected = !industries[i - 1].selected
}
}
this.setState({ industries: industries })
}
HOPE this helps.... Happy coding!!
I searched for the solution and found a library named react-native-tag-select that fulfilled my requirement perfectly where I can customise the view according to my requirement. Thanks all who answered.
Adding some code sample:
Setup:
npm install --save react-native-tag-select
or
yarn add react-native-tag-select
Usage:
import { TagSelect } from 'react-native-tag-select';
export default class App extends React.Component {
render() {
const data = [
{ id: 1, label: 'Money' },
{ id: 2, label: 'Credit card' },
{ id: 3, label: 'Debit card' },
{ id: 4, label: 'Online payment' },
{ id: 5, label: 'Bitcoin' },
];
return (
<View style={styles.container}>
<TagSelect
data={data}
itemStyle={styles.item}
itemLabelStyle={styles.label}
itemStyleSelected={styles.itemSelected}
itemLabelStyleSelected={styles.labelSelected}
/>
</View>
);}

Flatlist won't scroll; cells rendering with extra space

I'm having this very strange problem. When I render a list of products with a FlatList, it's putting this giant space between my cells. (I've commented out the background image to speed loading, but it behaves the same either way)
ProductsListScreen.js
class ProductsListScreen extends Component<Props> {
render() {
return <WithFlatList products={this.props.products} />;
// return <WithMap products={this.props.products} />;
}
}
export default connect(({ productsReducer }) => ({
products: Object.values(productsReducer.products)
}))(ProductsListScreen);
const WithFlatList = ({ products }) => {
return (
<FlatList
data={products}
renderItem={({ item }) => <ProductListCellView product={item} />}
keyExtractor={item => `${item.id}`}
/>
);
};
const WithMap = ({ products }) => {
return (
<ScrollView contentContainerStyle={styles.container}>
{products.map(p => (
<ProductListCellView product={p} key={p.id} />
))}
</ScrollView>
);
};
const styles = {
container: {
flex: 1,
height: "100%"
}
};
ProductsListCellView.js
const ProductListCellView = ({ product }: Props) => {
return (
<View style={styles.cellContainer}>
<ImageBackground
// source={{ uri: product.images[0].src }}
style={styles.backgroundImage}
imageStyle={styles.imageStyle}
>
<View style={styles.textContainer}>
<NameText> {product.name} </NameText>
<PriceText> ${product.price} </PriceText>
</View>
</ImageBackground>
</View>
);
};
export default ProductListCellView;
const styles = {
cellContainer: {
borderBottomWidth: 0.5,
borderBottomColor: "grey",
width: "100%",
height: "50%",
borderWidth: 3,
backgroundColor: "lightblue"
},
backgroundImage: {
width: "100%",
height: "100%",
justifyContent: "center"
},
imageStyle: {
height: "140%",
width: "140%",
left: "-20%",
top: "-20%"
},
textContainer: {
backgroundColor: "black",
maxWidth: "50%",
padding: 5,
opacity: 0.75
}
};
const baseSize = 14;
const text = {
name: {
fontSize: baseSize + 8,
fontWeight: "bold",
color: "white"
},
price: { fontSize: baseSize + 4, color: "white" }
};
const NameText = props => <Text style={text.name}>{props.children}</Text>;
const PriceText = props => <Text style={text.price}>{props.children}</Text>;
It seems that whatever I set the height for cellContainer at, it renders the cell at that % of the screen (or of some container that seems based on screen height), and then the cell contents at the same % of the cell.
Also, the list isn't scrolling. I can see the next cell peeking out the bottom so the whole list is rendering, but it just bounces back when I try to scroll. I've tried wrapping various things in ScrollView with no luck. (I changed the cellContainer height to 15% in the screenshot below)
When I map the items manually (switching the return in the above code to use `, the height works fine, but the scrolling still doesn't work:
Has anybody else had this problem?
Rather than setting the height of cellContainer to a % value, set it to a static height, or using padding to automatically size each item.

React-Native -> Couldn't change style using state

I've got a problem with my app. I'm trying to change style in my child component using states but unfortunately i dont get any results. I tried evrythig i know, but i dont know what is going on and why styles aren't changing at all
Here is parent :
constructor(props) {
super(props);
this.state = {
number: 1,
cords: [
],
choosenCords: [
],
style: {flex:1,flexDirection:'row',backgroundColor:'red'}
};
}
<View style={{ marginTop: 3 }}>
<ListItem style={this.state.style} test={this.state.test} title={item.timestamp} context={item.longtitude + " " + item.latitude} click={this.getData.bind(this, item.longtitude, item.latitude, item.timestamp)} />
</View>
getData = async(x, y, z) => {
let tempTab = []
tempTab.push(x)
tempTab.push(y)
tempTab.push(z)
const changeTest = this.state.test * (-1)
await this.setState({
choosenCords: [...this.state.choosenCords, tempTab],
style: {flex:1,flexDirection:'row',backgroundColor:'blue'}
})
}
}
This fragment of code represent changing style onPress. "choosenCords" are changing, but "style" don't.
Here is child:
class ListItem extends Component {
constructor(props) {
super(props);
this.state = {
test:this.props.test
};
}
render() {
return (
<View style={this.props.style}>
<Image
source={require('../Images/gps.png')}
style={{ borderWidth: 3, borderColor: '#7887AB', borderRadius: 50, marginLeft: 15, marginTop: 10 }}
/>
<View style={{ flex: 1, flexDirection: "column", marginBottom: 15, marginLeft: 10 }}>
<Text style={{ marginLeft: 10, fontSize: 16 }}>
{
"Timestamp: " + this.props.title
}
</Text>
<Text style={{ marginLeft: 15, fontSize: 15 }}>
{
"Cords:" + this.props.context
}
</Text>
</View>
<TouchableHighlight
underlayColor={'transparent'}
onPress={
this.props.click
}>
<Image
source={
require('../Images/check.png')
}
style={{ marginRight: 20 }}
/>
</TouchableHighlight>
</View>
);
}
Could someone help me with that?
You have to do like this:
const customStyle= this.state.test ? styles.custom_1: styles.custom_2
return <View
style={[ styles.anyStyle, customStyle]}
/>;
Define your custom style variable, set it based on state, then use it as an array for your element.
The child component does not automatically update when the state of the parent component changes. You'll need to update the state of the child component manually when the parent sends new props.
//in child component
componentDidUpdate(previousProps, previousState){
if(previousProps.style !== this.props.style){
//update child state here
this.setState({style: this.props.style})
}
}
Now instead of using "style={this.props.style}" in the child, use "style={this.state.style}"
It is happening because when you update your state, child component will not update automatically. you will get new updated value in componentWillReceiveProps() method.
your ListItem class will be like below:
class ListItem extends Component {
constructor(props) {
super(props);
this.state = {
test:this.props.test,
style:'' // take style into your state
};
}
componentWillMount(){
this.setState({style: this.props.style}) //set style
}
componentWillReceiveProps(newProps){
//called when you update style
if(newProps.style !== this.props.style){
this.setState({style: newProps.style})
}
}
render() {
return (
<View style={this.state.style}> //set style for views from state
// add your views here
</View>
);
}
}

React Native - Changing States in an Array of another file

Hey Everyone :) This is my first post here, hope I am doing everything correctly!
I am currently working on a school project and using react-native for some weeks now.
My Problem:
I have the file data.js:
const cardOne_1 = require("../images/Vergleiche/Eisbär.jpg");
const cardTwo_1 = require("../images/Vergleiche/Android.jpg");
const cardThree_1 = require("../images/Vergleiche/Han_Solo_Alt.jpg");
const cardOne_2 = require("../images/Vergleiche/Gorilla.jpg");
const cardTwo_2 = require("../images/Vergleiche/Apple.jpg");
const cardThree_2 = require("../images/Vergleiche/Han_Solo_Jung.jpg");
export default[
{
image: cardOne_1,
image2: cardOne_2,
text: '53%',
text2: '47%',
title: 'Icebear vs Gorilla',
check: false,
},
{
image: cardTwo_1,
image2: cardTwo_2,
text: '19%',
text2: '81%',
title: 'Android vs IOS',
check: true,
},
{
image: cardThree_1,
image2: cardThree_2,
text: '70%',
text2: '30%',
title: 'Han Solo',
check: false,
},
];
My Homescreen contains two of these Deckswipers (For better clarity I will show here only the code for the first one), which are used to compare two images:
Homescreen - With two DeckSwiper
import data from '../Data.js';
export default class SwipeCards2 extends Component {
_onSwipeLeft() {
this._deckSwiper1._root.swipeLeft();
this._deckSwiper2._root.swipeRight();
}
_onSwipeRight() {
this._deckSwiper2._root.swipeLeft();
this._deckSwiper1._root.swipeRight();
}
render() {
return (
<Container style={{ backgroundColor: '#ffffff' }}>
<View>
<DeckSwiper
ref={mr => (this._deckSwiper1 = mr)}
dataSource={data}
onSwipeRight={() => this._deckSwiper2._root.swipeLeft()}
onSwipeLeft={() => this._deckSwiper2._root.swipeRight()}
looping={true}
renderEmpty={() => (
<View style={{ alignSelf: 'center' }}>
<Text>Das war´s!</Text>
</View>
)}
renderItem={item => (
<Card
style={{
elevation: 3,
height: 335,
justifyContent: 'center',
width: Dimensions.get('window').width + 1,
marginLeft: -1,
marginTop: 0,
}}>
<TouchableWithoutFeedback onPress={() => this._onSwipeRight()}>
<CardItem cardBody style={{ alignItems: 'center' }}>
<Image
style={{
resizeMode: 'cover',
flex: 1,
height: 335,
}}
source={item.image}
/>
</CardItem>
</TouchableWithoutFeedback>
</Card>
)}
/>
</View>
</Container>
);
}
}
I want to set the state "check" in data.js to true, everytime the user does swipe to the right.
A Third Screen renders a List component, which should show the previous made decisions of the user. This list is based on "check" of data.js.
Screen 3 - List of all the decisions
I tried for almost three days and can not find any good solution!
Do you have any suggestions how to achieve this?
Thanks :)
I'm not sure how things work with this DeckSwiper component but since you are importing a static data, if you need to change the data you need to clone it and then change it. Assigning data clone to a state variable and then giving it to the component will reflect the changes to the component.
To change a property on a specific object in your array you also need an unique identifier like an ID or similar.
Example
import data from '../Data.js';
export default class SwipeCards2 extends Component {
constructor(props) {
super(props);
// clone the static data to state
this.state = {
data: [...data]
}
}
changingCheckFunction(obejctsUniqueId) {
this.setState((prevState) => {
// find the object's id
const itemIndex = prevState.data.findIndex(x => x.id == obejctsUniqueId);
// copy the item and assign the new checked value
const newItem = Object.assign({}, prevState.data[itemIndex], { checked: !prevState.data[itemIndex]});
// copy the previous data array
const newData = [...prevState.data];
// set the newItem to newData
newData[itemIndex] = newItem;
// return the new data value to set state
return { data: newData };
});
}
_onSwipeLeft() {
this._deckSwiper1._root.swipeLeft();
this._deckSwiper2._root.swipeRight();
}
_onSwipeRight() {
this._deckSwiper2._root.swipeLeft();
this._deckSwiper1._root.swipeRight();
}
render() {
return (
<Container style={{ backgroundColor: '#ffffff' }}>
<View>
<DeckSwiper
ref={mr => (this._deckSwiper1 = mr)}
dataSource={this.state.data}
onSwipeRight={() => this._deckSwiper2._root.swipeLeft()}
onSwipeLeft={() => this._deckSwiper2._root.swipeRight()}
looping={true}
renderEmpty={() => (
<View style={{ alignSelf: 'center' }}>
<Text>Das war´s!</Text>
</View>
)}
renderItem={item => (
<Card
style={{
elevation: 3,
height: 335,
justifyContent: 'center',
width: Dimensions.get('window').width + 1,
marginLeft: -1,
marginTop: 0,
}}>
<TouchableWithoutFeedback onPress={() => this._onSwipeRight()}>
<CardItem cardBody style={{ alignItems: 'center' }}>
<Image
style={{
resizeMode: 'cover',
flex: 1,
height: 335,
}}
source={item.image}
/>
</CardItem>
</TouchableWithoutFeedback>
</Card>
)}
/>
</View>
</Container>
);
}
}

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