react native onTextLayout event works in - reactjs

How can I force the Text element on IOS to read the proper amount of lines in the onTextLayout event, I want to implement read more and initially I slice the text to have 60 characters, it gives 1,5 lines of text. On button press I swap the Text components and display the full text, on this Text component with the full text I have onTextLayout event, and when it fires it has only the truncated text available, how can I force refresh or something like that so that it would use the proper full text and measure lines for a full text? On android, it works fine, here is the code
const TextContent = ({ content }: TextContentProps) => {
const [fullDisplay, setFullDisplay] = useState(false);
const height = useSharedValue(30);
const animatedStyle = useAnimatedStyle(() => {
return {
height: height.value,
};
});
return (
<Animated.View style={animatedStyle}>
{fullDisplay ? (
<Text
style={styles.textContent}
onTextLayout={({ nativeEvent }) => {
height.value = withSpring(nativeEvent.lines.length * nativeEvent.lines[0].height);
}}
>
{content}
</Text>
) : (
<Text style={styles.textContent}>{content.slice(0, 60)}...</Text>
)}
{!fullDisplay && (
<Pressable style={styles.moreButton} onPress={() => setFullDisplay(true)}>
<Text style={styles.moreButtonText}>Więcej</Text>
</Pressable>
)}
</Animated.View>
);
};
and after the read more button click this text always ends up as a one-liner overflowing the parent element with the lines count eq 1. Thanks

Related

Passing a button select choice back to previous screen

So after some research, I have learned how to make a button that will take the user to another screen, and provide them a text input where they can enter some words, then on pushing the done button take them back to the previous screen where what they typed will be displayed. But for my particular needs, I am trying to figure out how to instead of a text input have a selection of buttons, such as "large, medium, small" and have that button select the data that would be displayed instead, and return them to the previous page where it is displayed.
initial screen
function HomeScreen( route ) {
navigation = useNavigation();
React.useEffect(() => {
if (route.params?.post) {
}
}, [route.params?.post]);
return (
<View>
<Pressable
title="Create post"
onPress={() => navigation.navigate('CreatePost')}
>
<Text style={{ margin: 10 }}>Post: {route.params?.post}</Text>
</Pressable
</View>
);
}
button selection screen
function CreatePostScreen( route ) {
const navigation = useNavigation();
const [postText, setPostText] = React.useState('');
return (
<>
<Pressable
title="Done"
onPress={() => {
navigation.navigate({
name: 'Home',
params: { postText },
merge: true,
});
}}
>
<Text>
Large
</Text>
</Pressable>
</>
);
}
any insight is greatly appreciated.
You can pass the data in form of object
{post: postText,
buttonType: 'medium'}
For getting the data
React.useEffect(() => {
if (route.params?.post) {
var buttonType= route.params?.buttonType
}
}, [route.params?.post]);
You can store the button type in a variable or state
var buttonType = route.params?.buttonType
Or You can try with useState() hooks
const [buttonType, setButtonType]=useState("")
setButtonType(route.params?.buttonType)
The for using it just do the following
<Text>{buttonType}</Text>
Please follow the React-Documentation

React Native rerendering UI after using array.map

I am attempting to have an icon switch its visual when clicked (like a checkbox). Normally in react native I would do something like this:
const [checkbox, setCheckbox] = React.useState(false);
...
<TouchableHighlight underlayColor="transparent" onPress={() => {setCheckbox(!setCheckbox)}}>
{added ? <MaterialIcons name="playlist-add-check" size={40} />
: <MaterialIcons name="playlist-add" size={40} />}
</TouchableHighlight>
However I have made some changes, and now I can't seem to replicate this behavior. I am using AsyncStorage class to storage and get arrays of objects for display. For simplification, in the example below I removed the storage code, and the objects each have an 'id' and an 'added' attribute, which is essentially the boolean value of the checkbox.
I am now attempting to update the icon shown to the user whenever it is pressed. I know the function is being called, but it will not update the icon. I am using array.map to create the list of icons. I created a demo here, and the code is below: https://snack.expo.dev/#figbar/array-map-icon-update
const templateObject = {
id: 0,
added: false,
};
const templateObject2 = {
id: 1,
added: true,
};
export default function App() {
const [savedNumbers, setSavedNumbers] = React.useState([]);
React.useEffect(() => {
setSavedNumbers([templateObject,templateObject2]);
}, []);
const populateSavedNumbers = () =>
savedNumbers.map((num, index) => <View key={index}>{renderPanel(num.id,num.added)}</View>);
const updateNumber = (id) => {
let capturedIndex = -1;
for(var i = 0; i < savedNumbers.length; i += 1) {
if(savedNumbers[i].id === id) {
capturedIndex = i;
break;
}
}
let _tempArray = savedNumbers;
_tempArray[capturedIndex].added = !_tempArray[capturedIndex].added;
setSavedNumbers(_tempArray);
}
const renderPanel = (id:number, added:boolean) => {
return (
<View>
<TouchableHighlight underlayColor="transparent" onPress={() => {updateNumber(id);}}>
{added ? <MaterialIcons name="playlist-add-check" size={40} />
: <MaterialIcons name="playlist-add" size={40} />}
</TouchableHighlight>
</View>
);
}
return (
<View>
<View>buttons:</View>
<View>{populateSavedNumbers()}</View>
</View>
);
}
This is a common React pitfall where things don't re-render when it seems like they should. React does shallow comparisons between new and old states to decide whether or not to trigger a re-render. This means that, when declaring a variable to simply equal a state variable which is an object or an array, a re-render is not triggered since those two variables now reference the same underlying data structure.
In this case, you are setting _tempArray to reference the array savedNumbers rather than creating a new array. Therefore, React's shallow comparison comes back as "equal", and it doesn't believe that a re-render is necessary.
To fix this, change this line:
let _tempArray = savedNumbers;
to this:
let _tempArray = [...savedNumbers];

TextInput goes Missing, but Keyboard Stays After Every KeyStroke?

Trying to figure out an issue with the TextInput component I have which is acting weird. Basically, after every keystroke, the textinput seems to be losing its focus, but the keyboard seems to stay..
Im implementing it as a search bar that is triggered / animated when a search icon is touched by the user:
<TouchableHighlight
activeOpacity={1}
underlayColor={"#ccd0d5"}
onPress={onFocus}
style={styles.search_icon_box}
>
onFocus method:
const onFocus = () => {
setIsFocused(true);
const input_box_translate_x_config = {
duration: 200,
toValue: 0,
easing: EasingNode.inOut(EasingNode.ease)
}
const back_button_opacity_config = {
duration: 200,
toValue: 1,
easing: EasingNode.inOut(EasingNode.ease)
}
// RUN ANIMATION
timing(input_box_translate_x, input_box_translate_x_config).start();
timing(back_button_opacity, back_button_opacity_config).start();
ref_input.current.focus();
}
just a simple animation where when triggered the search bar will slide from the right side of the screen
<Animated.View
style={[ styles.input_box, {transform: [{translateX: input_box_translate_x}] } ]}
>
<Animated.View style={{opacity: back_button_opacity}}>
<TouchableHighlight
activeOpacity={1}
underlayColor={"#ccd0d5"}
onPress={onBlur}
style={styles.back_icon_box}
>
<MaterialIcons name="arrow-back-ios" size={30} color="white" />
</TouchableHighlight>
</Animated.View>
<SearchBar
placeholder='Search'
keyboardType='decimal-pad'
returnKeyType='done'
ref={ref_input}
value={searchText}
onChangeText={search}
onClear={onBlur}
onSubmitEditing={onBlur}
onFocus={() =>console.log("focus received" ) }
onBlur={() => console.log("focus lost") }
/>
</Animated.View>
Search
const search = (searchText) => {
setSearchText(searchText);
let filteredData = AnimalList.filter(function (item) {
return item.tag_number.toString().includes(searchText);
});
setFilteredData(filteredData);
}
So, when I clicked on the search icon, the search bar will present itself through animated view and the keyboard will automatically be focused. However, after entering a single character on the keyboard the searchbar just vanishes with keyboard still showing.
I tried to debug using onFocus={() =>console.log("focus received" ) } and it looks like the searchBar is still focused on, its just not showing
EDIT: Issue Video Here https://github.com/renwid/test/issues/1
You can show the full version of SearchBar to get more help
[Updated]
The initial step, Animated.Value must be
const input_box_translate_x = useRef(new Value(width)).current;
const back_button_opacity = useRef(new Value(0)).current;
instead of
const input_box_translate_x = new Value(width);
const back_button_opacity = new Value(0);
Because you using the function component and re-render might be re-create the component and the Animated.Value will be re-create too. So the Animated.Value can not keep the state and cause you issue

Only show redo search button when user has completely moved to a new area

How can i only show the redo button only when the user has changed region?In my code right now it keeps flickering on and off as the region changes, not sure what is missing.
Code:
app.js
onregionchange() {
this.setState({redosearch: !this.state.redosearch })
}
render() {
const Showredo = ({redosearch }) => redosearch ? <View><Text> redo now <Text></View> : null
return(
<View>
{this.state.redosearch ? <ShowRedo redosearch={this.state.redosearch }/> : null}
<View>
<MapView
ref={(map) => (this.map = map)}
style={styles.map}
onRegionChange={this.onregionchange}
>
</MapView>
)
}
There are a few things I notice may be causing an issue here. First, it looks like you may be double checking the state value; inside the Showredo element property and again inside the return value. second, The code has two open View tags and two Text tags with no close. Third, I can't see if the function onregionchange is bound or not. And finally, you're returning two elements in the render function (or actually missing two view closing tags at the end)
try to change your code to this which should correct all of those:
onregionchange = () => {
this.setState({redosearch: !this.state.redosearch })
}
render() {
const { redosearch } = this.state;
return([
<View key="a_key_for_element_1">
{redosearch ? <View><Text> redo now </Text></View> : null}
</View>,
<MapView
key="a_key_for_element_2"
ref={(map) => (this.map = map)}
style={styles.map}
onRegionChange={this.onregionchange}
>
</MapView>]
);
}

remove text from button that created in loop

I'm using react native to build an App
I setup a loop that create button with letter inside each button
I want that onPress will run function that delete the letter inside the button that pressed
So far my code is as follow:
let LettersBoxes = [];
var test = [];
for (let i = 0; i < answerlen; i++) {
console.log(test)
let letter = answer[mixingLetters[i]]
console.log('check',letter)
LettersBoxes.push(
<TouchableOpacity onPress={() => this.onClick(letter, test)} style={styles.boxStyle} key={i}>
<Image
source={require('../../img/parchment3.gif')}
style={{width: 40, height: 40, alignItems: 'center'}}
>
<View>
<Text
style={{fontSize:28, fontWeight: 'bold',}}>
{letter}
</Text>
</View>
</Image>
</TouchableOpacity>
);
}
return(
<View style={styles.viewStyle}>{LettersBoxes}</View>
)
}
};
Is it possible to do what I am trying to do?
If you want to create components dynamically, with a button created for each letter in an array of letters, you can do something like the following (inside the render method).
const letters = ['a', 'b', 'c'];
const buttons = letters.map(letter => (
<Button>{letter}</Button>
);
return (
<View>
{buttons}
</View>
);
I have omitted many things to focus on the functionality. Also, instead of "Button" you can use TouchableOpacity and add the missing props.
Essentially, given a list of letters, you want to map each letter to a button component whose content includes the letter. Then, React knows how to render an array of components by simply saying the variable name in the return part in JSX.
Now, to delete the content of a button that is clicked ("deleting the letter"), you would have to move the list of letters to the component state. There you could define a data structure like:
// component state
{
letters: {
a: true,
b: false,
c: true,
// ...
}
}
In the above structure, if this.state.letters['a'] is true, you show the letter; otherwise, you don't show it. Simply put, do the following when you map:
const buttons = Object.keys(this.state.letters).map(letter => {
if (this.state.letters[letter]) {
return <Button onClick={() => onSomethingClick(letter)}>{letter}</Button>;
}
else { // no letter should appear, so empty content
return <Button onClick={() => onSomethingClick(letter)}></Button>;
}
}
where the onClick event handler method is defined as something like:
onSomethingClick = (letter) => {
this.setState({
letters: {
...this.state.letters, // use Object.assign if you can't use spread operator
[letter]: !this.state.letters[letter] // switches T to F and vice-versa
}
}
}

Resources