changing fontSize of TextInput in a smooth way - react-native

I've created a text input where the length of the text entered determines the font size of the text.
The idea is to make something similar to the way Instagram has it: https://media.giphy.com/media/1AgCjLrT0XWhUh4UqY/giphy.gif
Now, this is what I'm able to do, that does things abruptly giving a bad user experience (reason being delay in updating fontSize): https://media.giphy.com/media/p37FFA1RhCuRHxIH2P/giphy.gif
This is my code:
render() {
let updatedFontSize = 32;
if(this.state.inputText.length > 0) {
updatedFontSize = 150 - 8*(this.state.inputText.length);
updatedFontSize = (updatedFontSize < 32 ? 32 : updatedFontSize);
}
return (
<TextInput
style={{ fontSize: updatedFontSize }}
onChangeText={ (inputText) => { this.setState({inputText}) } }
value={this.state.inputText}
multiline={true}
maxLength={25}
/>
)
}

Let's think about how to approach this.
The requirements are:
The effect should only happen with at least 3 characters in the text field
There's a maximum and minimum size that the font size can reach
The font should get smaller if the new text incoming is longer than the previous value.
The font should get larger if the new text incoming is shorter than the previous value.
Given these requirements based on the gif, we can come up with a clean solution.
changeText = value => {
if (value.length > 3) {
if (
this.state.value.length < value.length &&
this.state.fontSize > MIN_FONT_SIZE
) {
this.setState(({ fontSize }) => ({ fontSize: fontSize - INCREMENT, value }));
} else if (
this.state.value.length > value.length &&
this.state.fontSize < MAX_FONT_SIZE
) {
this.setState(({ fontSize }) => ({ fontSize: fontSize + INCREMENT, value }));
}
}
};
Here's the fully working example, https://snack.expo.io/#roach_iam/increasing-fontsize-textinput.

Related

if-else statement inside jsx not working as expected

I am trying to animate an image in and out when clicked using React Native reanimated, but the JSX if else condition is not working quite right. Below is the code that works, but only works when clicked for the first time. The state toggled is set to true, so when clicked again it should set the size back to the original and the image should animate back to it and vice versa.
The setup is like so:
export default () => {
const newNumber = useSharedValue(100);
const [Toggled, setToggled] = useState(false);
const style = useAnimatedStyle(() => {
return {
width: withSpring(newNumber.value),
height: withSpring(newNumber.value, { damping: 10,
stiffness: 90, }),
};
});
onPress={() => {
{Toggled ? newNumber.value = 100 : newNumber.value = 350; setToggled(true)}
}}
The problem is when I try to add the newNumber.Value = 100 when setting Toggled to false, it gives me an error. I try to add it like this :
{Toggled ? newNumber.value = 100; setToggled(false) : newNumber.value = 350; setToggled(true)}
Why does it accept the first one but not the second?
If I use this, it works, but can it be done the other way?
const isToggled = Toggled;
if (isToggled) {
// alert('Is NOT Toggled');
newNumber.value = 100;
setToggled(false)
} else {
// alert('Is Toggled');
newNumber.value = 350;
setToggled(true)
}
Thanks
It looks you are just setting a value to newNumber base on Toggled, and then setting the Toggled to its opposite value. So why not do
onPress={() => {
newNumber.value = Toggled ? 100 : 350
setToggled(prev=>!prev)
}}

React Native create text highlight styled Text component in Text component by function

I am currently working on small project.
What I am trying to do is to create text hightlight in my speech to text react native app
My project has Flask API server to detect text data whether it is voice phishing or not.
I am using [react-native-voice]https://github.com/react-native-voice/voice for speech to text
my react native app will send text data segment every 5 seconds to Flask API server to find out whether it is voice phishing or not then Flask API server will return score that is 0 to 1 so over 0.5 is voice phishing
anyway what i am trying to say is that when the text data is considered to be voice phishing (score over 0.5) text data on my app will be highlighted
here is my TextBox Component code
is that possible my setHighlight function
input
hi my name is woo young from Well Bing Bank give me your account my number is 112-22314-12314. Thank you.
return
hi my name is woo young from Well Bing Bank
<Text style={styles.highlighted}>
give me your account
<Text>
my number is 112-22314-12314. Thank you.
but it returns
hi my name is woo young from Well Bing Bank
[object Object]
my number is 112-22314-12314. Thank you.
import { Text, View, StyleSheet } from "react-native";
const NO_WIDTH_SPACE = '​';
const highlight = string =>
string.split(' ').map((word, i) => (
<Text key={i}>
<Text style={styles.highlighted}>{word} </Text>
{NO_WIDTH_SPACE}
</Text>
));
export default class TextBox extends Component {
setHighlight = (string, array) => {
for (let i = 0; i < (array.length)/2; i++) {
let seg = string.slice(array[2*i], array[2*i+1])
let firstIdx = string.slice(0, array[2*i-1])
let lastIdx = string.slice(array[2*i+2], array.length)
let segText = highlight(seg)
let result = firstIdx + segText + lastIdx
return result
}
}
render = () => {
return(
<View style={styles.textBox}>
{this.props.partialResults.map((result, index) => {
const hightlightText = this.setHighlight(result,[3,4])
return (
<Text style={styles.resultText} key={`partial-result-${index}`}>
{ hightlightText }
</Text>
)
})}
</View>
)
}
}
const styles = StyleSheet.create({
textBox: {
marginTop: 10,
width: 330,
height: 400,
backgroundColor: "white",
borderWidth: 2,
padding: 10,
borderColor: "dodgerblue"
},
resultText: {
fontSize: 18
},
highlighted: {
padding:2,
backgroundColor: 'red',
},
})
Not sure if your logic is sound but you can return the result as JSON Stringify, that may indicate what that object contains and give you a better idea what to return.
setHighlight = (string, array) => {
for (let i = 0; i < (array.length)/2; i++) {
let seg = string.slice(array[2*i], array[2*i+1])
let firstIdx = string.slice(0, array[2*i-1])
let lastIdx = string.slice(array[2*i+2], array.length)
let segText = highlight(seg)
let result = firstIdx + segText + lastIdx
return JSON.stringify(result)
}
}

React Native TextInput masking is buggy

I'm trying to make a masked TextInput for BRL currency using regex, so every time the user types a number it is formated as follow:
First the number pressed appears inside the TextInput and then you can change the state to your desired value. Isn't there a way to change the text before it is displayed for the user?
Basically what I'm doing is:
const MoneyTextInput = ({onChangeText, ok}) => {
let [text, setText] = useState('00,00');
return (
<TextInput
value={text}
keyboardType="number-pad"
style={{
width: '100%',
height: '100%',
fontSize: 22,
}}
onChange={({nativeEvent}) => {
setText(prettifyCurrency(nativeEvent.text));
onChangeText(prettifyCurrency(nativeEvent.text));
}}
/>
);
};
And the function with the regex is this:
function prettifyCurrency(value: String): String {
if (!value) {
return '00,00';
}
let newText = value.replace(/(\D)/g, '').replace(/^0+/, '');
if (newText.length < 4) {
for (let i = newText.length; i < 4; i++) {
newText = '0' + newText;
}
}
newText = newText
.replace(/(\d{2}$)/g, ',$&')
.replace(/(\d{1})(\d{3})([,])/, '$1.$2$3')
.replace(/(\d{1})(\d{3})([.])/, '$1.$2$3')
.replace(/(\d{1})(\d{3})([.])/, '$1.$2$3');
return newText;
}
Shouldn't it wait for the value prop to be updated? Is there anyway I achieve what I want? Because I know in Android (Java) I had a function to change the text before it was displayed.
PS.: I'm using OnChange function because I found it was a little bit faster than onChangeText

How to replace #mention in string with a Text Componet with link - React Native

I am using React Native. What I need to do is from a text that I extract from the DB to apply a format (font color, link) to make #mentions, when searching if in the text finds 1 single match makes replacement all good, but if there are several #mentions in the text, it throws me an error.
Text example:
hey what happened #-id:1- everything ok ?? and you #-id:2- #-id:3- #-id:4-
//// listusers its array, example: [idusuario: "1", usuario: "#luigbren", format: "#-id:1-".....]
const PatternToComponent = (text, usuarios,) => {
let mentionsRegex = new RegExp(/#-(id:[0-9]+)-/, 'gim');
let matches = text.match(mentionsRegex);
if (matches && matches.length) {
matches = matches.map(function (match, idx) {
let usrTofind = matches[idx]
//////////////////////////////////////////////////////////////////
const mentFormat = listusers.filter(function (item) {
const itemData = item.format;
return itemData.indexOf(usrTofind) > -1;
});
if (mentFormat.length > 0) {
let idusuario = mentFormat[0].idusuario
let replc = mentFormat[0].usuario
console.log(usrTofind) //// here find #-id:1-
console.log(replc) //// here is #luigbren for replace
////////// now here replace part of the string, #-id:1- with a <Text> component
///////// with #luigbren and the link, this is repeated for every #mention found
parts = text.split(usrTofind);
for (var i = 1; i < parts.length; i += 2) {
parts[i] = <Text key={i} style={{ color: '#00F' }} onPress={() => { alert('but this is'); }}>{replc}</Text>;
}
return text = parts;
/////////////////////////////////////////////////////////////////////
} else {
return text
}
});
} else {
return text
}
return text
};
in this way the code works well for me only when in the text there is only one mention, example 'hey what happened #-id:1- everything ok ??' , but when placing more than one mention it gives me an error, example: 'hey what happened #-id:1- everything ok ?? #-id:2- #-id:3-' ...
Error:
TypeError: text.split is not a function
And if instead of placing parts = text.split(usrTofind); I place parts = text.toString().split(usrTofind); it gives me this error:
[Object Object]
Update
I found the solution. I found another simple way to replace part of the text with the <Text> component, using this method:
funcMentToLinks = (text, idusuario) => {
const regex = /\#[id:([0-9]+]/g;
const splitText = text.split(regex)
if (splitText.length <= 1) {
return text;
}
const matches = text.match(regex);
return splitText.reduce(function (arr, element, index) {
if (matches[index]) {
return [arr, element, <Text key={index} style={{ backgroundColor: '#ceedf1', fontWeight: 'bold', color: '#4f73c4' }} onPress={() => this.callmyopc(idusuario)}>{replc}</Text>,]
} else {
return [arr, element]
}
}, []);
}
but now I have a problem, I can't find a way to call a function to navigate to another section or invoke direct the this.props.navigation.navigate('opc'), if i put my function .. example: onPress={() => this.callmyopc(idusuario)} gives me an error
Error:
_this2.callmyopc is not a function
if i put it this way onPress={() => {this.props.navigation.navigate('opc', { idperf: idusuario })}} gives me an error..
Error:
Cannot read property 'navigation' of undefined
Note: the functions this.props.navigation.navigate if they work for me in this same file, I use it in other functions, but in this function I cannot invoke it.

How to get the current number of lines in String of TextInput?

After entering text in TextInput
I want to know the currunt number of lines in TextInput.
Or
The current number of strings is also possible.
I've tried string.split('\n').length, but this code does not detect that the line is automatically incremented when the text is larger than the screen.
How do I get the number of lines
When I make this feature in Swift
Implemented using the string.enumerateLines function.
Do you have a similar function?
As far as I know, there is no offical way to get the currently used number of lines, but I have a workaround for you:
We make use of the onLayout method of our TextInput and we keep track of the currently used height. We need two state variables:
this.state = {
height: 0, // here we track the currently used height
usedLines: 0,// here we calculate the currently used lines
}
onLayout:
_onLayout(e) {
console.log('e', e.nativeEvent.layout.height);
// the height increased therefore we also increase the usedLine counter
if (this.state.height < e.nativeEvent.layout.height) {
this.setState({usedLines: this.state.usedLines+1});
}
// the height decreased, we subtract a line from the line counter
if (this.state.height > e.nativeEvent.layout.height){
this.setState({usedLines: this.state.usedLines-1});
}
// update height if necessary
if (this.state.height != e.nativeEvent.layout.height){
this.setState({height: e.nativeEvent.layout.height});
}
}
Render Method
render() {
console.log('usedLines', this.state.usedLines);
return (
<View style={styles.container}>
<TextInput multiline style={{backgroundColor: 'green'}} onLayout={(e)=> this._onLayout(e)} />
</View>
);
}
Working Example:
https://snack.expo.io/B1vC8dvJH
For react-native version >= 0.46.1
You can use onContentSizeChange for a more accurate line track, for example with react hooks as following:
/**
* used to tracker content height and current lines
*/
const [contentHeightTracker, setContentHeightTracker] = useState<{
height: number,
usedLines: number;
}>({
height: 0,
usedLines: 0
});
useEffect(() => {
// console.log('used line change : ' + lineAndHeightTracker.usedLines);
// console.log('props.extremeLines : ' + props.extremeLines);
if (contentHeightTracker.usedLines === props.extremeLines) {
if (extremeHeight.current === undefined) {
extremeHeight.current = contentHeightTracker.height;
}
}
//callback height change
if (contentHeightTracker.height !== 0) {
props.heightChange && props.heightChange(contentHeightTracker.height,
contentHeightTracker.usedLines >= props.extremeLines,
extremeHeight.current);
}
}, [contentHeightTracker]);
const _onContentHeightChange = (event: NativeSyntheticEvent<TextInputContentSizeChangeEventData>) => {
// console.log('event height : ', event.nativeEvent.contentSize.height);
// console.log('tracker height : ', lineAndHeightTracker.height);
// the height increased therefore we also increase the usedLine counter
if (contentHeightTracker.height < event.nativeEvent.contentSize.height) {
setContentHeightTracker({
height: event.nativeEvent.contentSize.height,
usedLines: contentHeightTracker.usedLines + 1
});
} else {
// the height decreased, we subtract a line from the line counter
if (contentHeightTracker.height > event.nativeEvent.contentSize.height) {
setContentHeightTracker({
height: event.nativeEvent.contentSize.height,
usedLines: contentHeightTracker.usedLines - 1
});
}
}
};
render() {
console.log('usedLines', this.state.usedLines);
return (
<View style={styles.container}>
<TextInput
multiline
style={{backgroundColor: 'green'}}
onContentSizeChange={_onContentHeightChange}
/>
</View>
);
}
The other solutions might fail if the user:
Highlights a lot of lines and deletes them all at once
Pastes a lot of lines at once
One way to solve this issue would be to set a lineHeight to the <TextInput> and use the onContentSizeChange prop:
<TextInput
style={{
lineHeight: 20,
}}
onContentSizeChange={e =>
console.log(e.nativeEvent.contentSize.height / 20) // prints number of lines
}
/>