React Native : onChangeText how to handle two events? - react-native

I am making a upload user introduction function ,
the idea is :
the input box of introduction is able to add a characters count ;
and is able to use formik to upload also ..
I have noticed that ,the onChangeText only able to handle one event once ..therefore ,I made a function ,in order to handle two events ...
However ,it dose't work ..
Could you please take a look my code ?
Thank you so much !
import React,{useState} from 'react';
import { StyleSheet,View,Text } from 'react-native';
import ErrorMsg from './ErrorMsg';
import { useFormikContext } from 'formik';
import AppTextInput from './AppTextInput';
import colors from '../config/colors';
function AppFormFliedUserInfo({name,width='90%',number,style,...otherProps}) {
const{setFieldTouched, handleChange, errors, touched }= useFormikContext();
const [value,setValue] = useState('');
function twoCalls(name){
handleChange(name);
setValue(text);
}
return (
<View style={[styles.container,{width:width} ,style]}>
<View style={styles.first_container}>
<ErrorMsg error={errors[name]} visible={touched[name]}/>
<AppTextInput
onBlur = {()=>setFieldTouched(name)}
onChangeText = {twoCalls}
width = {width}
number ={number}
maxLength={number}
{...otherProps}
/>
</View>
{/**Count */}
{number&&(
<View style={styles.count_container}>
<Text>{value === "" ? "0" : value.length}/{number}</Text>
</View>
)}
</View>
);
}
const styles = StyleSheet.create({
count_container : {
width : "100%",
alignItems : 'flex-end',
marginTop : 10,
},
container :{
padding: 15,
flexDirection: "column",
marginVertical : 10,
borderRadius: 25,
backgroundColor: colors.light,
},
first_container : {
flexDirection : "row",
flex : 1,
},
})
export default AppFormFliedUserInfo;

It looks like it's just a typo:
function twoCalls(name){
handleChange(name);
setValue(name); //Change "text" to "name"
}

Related

mobile app want graphical text to change font size per line automatically, like on tiktok/instagram alternating lines different sizes

On TikTok and Instagram, they can generate automatically text font size like this image, where alternating lines have different font sizes automatically. I'm trying to figure out how to code that in React Native for mobile IOS and Android: [[enter image description here](https://i.stack.imgur.com/vkhIo.jpg)](https://i.stack.imgur.com/XcjLq.jpg)
I couldn't figure it out. I made something that I'm not crazy about, which is just having a larger font on the first three lines and then a smaller font. See image: But I don't like it. enter image description here
I totally misunderstood what PixelRatio.getFontScale did. I thought it would provide the average width a single character takes up on screen. If you can find a way to get a rough estimate of the width of a single character, then this method will work link:
import { useEffect, useState } from 'react';
import { View, StyleSheet, Text, PixelRatio } from 'react-native';
import rnTextSize from 'react-native-text-size';
import reduce from 'awaity/reduce';
import useViewLayout from './useViewLayout';
const fontScale = PixelRatio.getFontScale();
const showLayoutValues = true
export default function MultiLineText({
width,
containerStyle,
textStyle1 = { fontSize: 16 },
textStyle2 = { fontSize: 22 },
str,
...textProps
}) {
// containerLayout will provide the max width each line can have
const [containerLayout, onContainerLayout] = useViewLayout();
// lines was created in a useMemo hook but I wasnt sure if
// useMemo could handle async
const [lines, setLines] = useState([]);
useEffect(() => {
const calcLines = async () => {
let words = str.split(' ').filter((s) => s.trim().length);
let newLines = await words.reduce(
async (prevPromise, curr, index) => {
const prev = await prevPromise;
let lineIndex = prev.length - 1;
let style = index % 2 == 0 ? textStyle1 : textStyle2;
const fontSize = style.fontSize;
// I wanted to use this https://github.com/aMarCruz/react-native-text-size/
// to measure text width but expo doesnt support it
const config = {
// if you exported from expo and link rnTextSize set this to true
useMeasureModule:false,
fontProps:style
}
const useMeasureModule = false;
let lineWidth = await getTextWidth(
prev[lineIndex],
fontSize,
config
);
let wordWidth = await getTextWidth(curr, fontSize, config);
// if currentLine can fit the next word add it
if (lineWidth + wordWidth < (width || containerLayout.width))
prev[lineIndex] += curr + ' ';
// or put it on the next line
else {
prev[lineIndex + 1] = curr + ' ';
}
return prev;
},
['']
);
setLines(newLines);
};
calcLines();
}, [str, containerLayout, width, textStyle1, textStyle2]);
return (
<>
{showLayoutValues && <Text>Container Layout: {JSON.stringify(containerLayout,null,4)}</Text>}
<View
style={[styles.container, containerStyle]}
onLayout={onContainerLayout}>
{lines.map((line, i) => (
<Text
{...textProps}
// to ensure that lines dont wrap
numberOfLines={1}
style={[textProps.style, i % 2 == 0 ? textStyle1 : textStyle2]}>
{line}
</Text>
))}
</View>
</>
);
}
const getTextWidth = async (str, fontSize, config={}) => {
const {fontProps, useMeasureModule} = config;
if (!useMeasureModule) {
// random mathing
return str.length * fontScale * fontSize ** 0.8;
}
let measure = await rnTextSize.measure({
...fontProps,
text: str,
});
return measure.width;
};
const styles = StyleSheet.create({
container: {
width: '100%',
},
});
And then in use:
export default function App() {
return (
<View style={styles.container}>
<MultiLineText
containerStyle={{
width: '100%',
backgroundColor: '#eef',
alignSelf: 'center',
alignItems: 'center',
}}
textStyle1={styles.text}
textStyle2={styles.text2}
str="I am really not sure as of how long this text needs to be to exceed at least 3 lines. I could copy and paste some stuff here but I think if I just type and type it would be quicker than googling, copying, and pasting"
/>
</View>
);
}
I just found out that onTextLayout is a thing. It gives about each line in the Text component, including info about the characters present on each line. This could be used to figure out where to break lines of text (no planned web support):
After tying the str prop to a text input it became very clear that it is ideal to prevent this component from re-rendering as much as possible so I made additional changes (demo)
import { useState, useCallback, useEffect, memo } from 'react';
import { View, StyleSheet, Text, ScrollView } from 'react-native';
// text lines will alternate between these styles
const defaultLineStyles = [
{ color: 'red', fontSize: 16 },
{ color: 'blue', fontSize: 22 },
{ color: 'green', fontSize: 28 },
];
function MultiLineText({
containerStyle,
lineStyles = defaultLineStyles,
str,
...textProps
}) {
const [lines, setLines] = useState([]);
// each time a substring is added to line,
// remove the substring from remainingStr
const [remainingStr, setRemainingStr] = useState('');
const onTextLayout = useCallback((e) => {
// the first line of text will have the proper styling
let newLine = e.nativeEvent.lines[0].text;
setLines((prev) => {
return [...prev, newLine];
});
// remove newLine from remainingStr
setRemainingStr((prev) => prev.replace(newLine, ''));
}, []);
// when str changes reset lines, and set remainingStr to str
useEffect(() => {
setLines([]);
setRemainingStr(str);
}, [str]);
return (
<>
<View style={[styles.container, containerStyle]}>
<ScrollView style={{ flex: 1 }}>
{lines.map((line, i) => (
<Text
{...textProps}
style={[textProps.style, lineStyles[i % lineStyles.length]]}>
{line}
</Text>
))}
</ScrollView>
{/* this view will be invisible*/}
{remainingStr.length > 0 && (
<View style={{ opacity: 0 }}>
<Text
{...textProps}
onTextLayout={onTextLayout}
style={[
textProps.style,
// use lines.length to get proper style
lineStyles[lines.length % lineStyles.length],
]}>
{remainingStr}
</Text>
</View>
)}
</View>
</>
);
}
const styles = StyleSheet.create({
container: {
width: '100%',
height:'50%'
},
});
// be careful when passing non memoized array/objects
export default memo(MultiLineText)
Its important to note that objects/arrays that arent memoized/state/refs will cause the memoized component to re-render, even if the values are static e.g
<MultiLineText
containerStyle={{
width: '100%',
height: 200,
backgroundColor: '#eef',
alignSelf: 'center',
alignItems: 'center',
}}
style={styles.defaultTextStyle}
str={text}
lineStyles={[styles.text,styles.text2]}
/>
containerStyle and lineStyles are getting new objects and arrays every time its parent component re-render, which will make MultiLineText re-render (even though its memoized). After moving the containerStyle to the stylesheet and memoizing lineStyles re-rendering becomes better:
const lineStyles = React.useMemo(()=>{
return [styles.text,styles.text2]
},[])
return (
<View style={styles.container}>
<TextInput onChangeText={setText} label="Enter some text" value={text} />
<MultiLineText
containerStyle={styles.textContainer}
style={styles.defaultTextStyle}
str={text}
lineStyles={lineStyles}
/>
</View>

React Native how to change the focus automatically to the next TextInput?

I would love to make a bank card number input function .
Here's the idea , when the user complete an input box ,it will focus on the next input box automatically.
Like the picture before :
The TextInput component(AutoTextInput):
import React from 'react';
import { StyleSheet, TextInput, View } from 'react-native'
import colors from '../config/colors';
import AppText from './AppText/AppText';
function AutoTextInput(
{input_width="100%",
input_limit,
input_maxLength =16,
input_keyboardType ='numeric',
title,
...ortherProps}) {
return (
<View style={styles.container,{width:input_width}}>
<AppText style={styles.title}>{title}</AppText>
<TextInput
style={styles.input_text}
maxLength = {input_maxLength}
keyboardType ={input_keyboardType}
{...ortherProps}
/>
</View>
);
}
const styles = StyleSheet.create({
container : {
flexDirection : 'column',
} ,
input_text: {
fontSize: 18,
color : colors.medium,
borderColor : colors.primary,
borderWidth :0.5,
padding :10,
borderRadius :5,
textAlign :'center',
},
title : {
fontWeight : 'bold',
},
})
export default AutoTextInput;
The Screen I use this component :
import React,{useState} from 'react';
import {StyleSheet, View} from 'react-native'
import AppText from './AppText/AppText';
import AutoTextInput from './AutoTextInput';
function Visa({title}) {
const [value,setValue] = useState("");
return (
<View style={styles.container}>
<AppText>Card Number</AppText>
<View style={styles.card_num_container}>
<AutoTextInput
input_maxLength ={4}
input_width="23%"
onChangeText={(text)=>setValue(text)}
placeholder ="0000"
/>
<AutoTextInput
input_maxLength ={4}
input_width="23%"
onChangeText={(text)=>setValue(text)}
placeholder ="0000"
/>
<AutoTextInput
input_maxLength ={4}
input_width="23%"
onChangeText={(text)=>setValue(text)}
placeholder ="0000"
/>
<AutoTextInput
input_maxLength ={4}
input_width="23%"
onChangeText={(text)=>setValue(text)}
placeholder ="0000"
/>
</View>
</View>
);
}
const styles = StyleSheet.create({
container : {
width :"100%",
height :"100%",
marginTop : 25,
flexDirection : 'column',
} ,
card_num_container :{
flexDirection : 'row',
width : "100%",
justifyContent : 'space-around',
},
})
export default Visa;
I have no idea how to make it automatically focus on the next input box ..Anyone can help ?
Thanks sooo much !
You can create an array of ref like this
const refs = React.useRef([])
Then pass those refs to the custom text input component then when the user submits 4 number on the input box focus the next input like this
<AutoTextInput
input_maxLength ={4}
input_width="23%"
onChangeText={(text)=>setValue(text)}
placeholder ="0000"
forwardedRef={refs[0]}
onSubmitEditing={() => refs[0 + 1].current.focus()}
forwardRef={refs[index]}/>
Inside AutoTextInput
<TextInput
style={styles.input_text}
ref={this.props.forwardedRef}
maxLength = {input_maxLength}
keyboardType ={input_keyboardType}
onSubmitEditing={this.props.onSubmitEditing}/>

swiping on react-native-snap-carousel is not working as expected

I am trying to use react-native-snap-carousel but however, the swiping effect is not working as expected - it is often difficult to swipe left and right, it requires user to swipe harder to move to another picture (as illustrated in the link below).
Swiping issue with React Native Snap Carousel
I am not able to find any documented soluton but I found one possible prop - swipeThreshold. I try various value, but still the issue persist.
Does anyone know the solution to this?
I suggest you to use react-native-image-slider.
it's flexible and easy to use.
https://www.npmjs.com/package/react-native-image-slider
I made a component named slider.js:
import React, { Component } from 'react';
import {
View,
StyleSheet,
Image,
} from 'react-native';
import ImageSlider from 'react-native-image-slider';
export default class Slider extends Component {
render() {
return (
<ImageSlider
loop
autoPlayWithInterval={3000}
images={this.props.dataSource}
customSlide={({ index, item, style, width }) => (
<View key={index} style={[style, styles.customSlide]}>
<Image source={{ uri: item }} style={styles.customImage} />
</View>
)}
/>
);
}
}
const styles = StyleSheet.create({
customImage: {
height: 180,
marginRight: 20,
marginLeft: 20,
borderWidth: 1,
borderRadius: 10,
marginTop: 8,
},
customSlide: {
backgroundColor: '#eee',
},
});
you can add this to your project and use it wherever you need it like this:
import Slider from '../component/slider';
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
images: [
'https://placeimg.com/640/480/nature',
'https://placeimg.com/640/480/tech',
'https://placeimg.com/640/480/animals',
'https://placeimg.com/640/480/tech',
],
}
render() {
return (
<View style={{flex: 1, backgroundColor: '#eee'}}>
<Slider dataSource={this.state.images} />
</View>
);
}
}

React-native : show content on two rows

I want to show 6 options per row instead of showing only one option per row.
I'm still new in react-native
Here is the code :
import React from 'react';
import {fromJS} from 'immutable';
import {ScrollView, StyleSheet, TouchableOpacity} from 'react-native';
import {Text} from 'src/components';
import Option from './OptionVariable';
import {checkOption} from 'src/modules/product/helper';
import {margin} from 'src/components/config/spacing';
class AttributeVariable extends React.Component {
onSelectOption = (option) => {
const {onSelectAttribute, attribute} = this.props;
onSelectAttribute(attribute.get('id'), attribute.get('name'), option.get('option'));
};
render() {
const {attribute, meta_data, variations} = this.props;
// Attribute selected
const attributeSelected = meta_data.find(attr => attr.get('id') === attribute.get('id') && attr.get('name') === attribute.get('name'));
return (
<>
<Text>
{attribute.get('name')}: <Text colorSecondary>{attributeSelected ? attributeSelected.get('option') : ''}</Text>
</Text>
<ScrollView
style={styles.attribute}
vertical
showsHorizontalScrollIndicator={false}>
{attribute.get('options').map(option => {
const disabled = meta_data.size === 0 ? false : !checkOption(variations, meta_data, fromJS({
id: attribute.get('id'),
name: attribute.get('name'),
option: option.get('option'),
}));
return (
<TouchableOpacity
activeOpacity={disabled ? 1 : 0}
key={option}
onPress={() => disabled ? {} : this.onSelectOption(option)}
>
<Option
type={attribute.get('type')}
selected={attributeSelected && option.get('option') === attributeSelected.get('option')}
disabled={disabled}
option={option}
/>
</TouchableOpacity>
)
})}
</ScrollView>
</>
);
}
}
const styles = StyleSheet.create({
attribute: {
marginTop: margin.small,
},
});
export default AttributeVariable;
Here is how it looks :
I hope everything is clear to you.
Sorry for my bad english it's not my main language....
Thanks in advance for any help.
you can say in your attribute style:
attribute: {
marginTop: margin.small,
flexDirection: 'row'
},
so it should align in rows instead of columns
That should help you to learn more about styling components in react native: https://facebook.github.io/react-native/docs/flexbox
Try using the Flatlist (instead of ScrollView) component passing the horizontal={true} props.
Like this:
FlatList
horizontal={true}
data={attribute.option}
renderItem={({ item }) => "Pass your component"}
keyExtractor={item => item.id}
/>
UPDATE 2 :
I used View instead of ScrollView with the following styles and it worked just fine..
<View style={[styles.container, ]}
horizontal
style={{AlignContent: 'flex-start', flexDirection: 'row', alignItems: 'flex-start',}}
style={styles.attribute}
showsHorizontalScrollIndicator={false}>
UPDATE 1 :
I solved the problem using this :
container: {
marginTop: margin.small,
flexDirection: 'row-reverse',
position: 'relative',
width: 'auto',
alignSelf: 'stretch',
maxWidth: 29,
height: 29,
marginBottom: margin.big,
borderRadius: borderRadius.base,
borderWidth: 1,
},
I changed the size of option so all of them appear on the same row (not exactly what I want but it works)
If anyone have a better answer please share.

setNativeProps Change Value for Text Component React Native Direct Manipulation

I want to directly update the value of a component due to performance reasons.
render(){
<View>
<Text style={styles.welcome} ref={component => this._text = component}>
Some Text
</Text>
<TouchableHighlight underlayColor='#88D4F5'
style={styles.button}>
<View>
<Text style={styles.buttonText}
onPress={this.useNativePropsToUpdate.bind(this)}>
Iam the Child
</Text>
</View>
</TouchableHighlight>
</View>
}
This is the method I use to update the text component. I dont know if I am setting the right attribute/ how to figure out which attribute to set:
useNativePropsToUpdate(){
this._text.setNativeProps({text: 'Updated using native props'});
}
Essentially trying to follow the same approach from this example:
https://rnplay.org/plays/pOI9bA
Edit:
When I attempt to explicitly assign the updated value:
this._text.props.children = "updated";
( I know this this the proper way of doing things in RN ). I get the error "Cannot assign to read only property 'children' of object'#'"
So maybe this is why it cant be updated in RN for some reason ?
Instead of attempting to change the content of <Text> component. I just replaced with <TextInput editable={false} defaultValue={this.state.initValue} /> and kept the rest of the code the same. If anyone know how you can change the value of <Text> using setNativeProps OR other method of direct manipulations. Post the answer and ill review and accept.
The text tag doesn't have a text prop, so
this._text.setNativeProps({ text: 'XXXX' })
doesn't work.
But the text tag has a style prop, so
this._text.setNativeProps({ style: { color: 'red' } })
works.
We can't use setNativeProps on the Text component, instead, we can workaround and achieve the same result by using TextInput in place of Text.
By putting pointerEvent='none' on the enclosing View we are disabling click and hence we can't edit the TextInput (You can also set editable={false} in TextInput to disbale editing)
Demo - Timer (Count changes after every 1 second)
import React, {Component} from 'react';
import {TextInput, StyleSheet, View} from 'react-native';
class Demo extends Component {
componentDidMount() {
let count = 0;
setInterval(() => {
count++;
if (this.ref) {
this.ref.setNativeProps({text: count.toString()});
}
}, 1000);
}
render() {
return (
<View style={styles.container} pointerEvents={'none'}>
<TextInput
ref={ref => (this.ref = ref)}
defaultValue={'0'}
// editable={false}
style={styles.textInput}
/>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 0.7,
justifyContent: 'center',
alignItems: 'center',
},
textInput: {
fontSize: 60,
width: '50%',
borderColor: 'grey',
borderWidth: 1,
aspectRatio: 1,
borderRadius: 8,
padding: 5,
textAlign: 'center',
},
});
export default Demo;
As setNativeProps not solving the purpose to alter the content of <Text />, I have used below approach and is working good. Create Simple React Component like below...
var Txt = React.createClass({
getInitialState:function(){
return {text:this.props.children};
},setText:function(txt){
this.setState({text:txt});
}
,
render:function(){
return <Text {...this.props}>{this.state.text}</Text>
}
});