React Native Swipeable (Swipe to delete) not closing - ios

I'm using Swipeable from React-Native-Gesture-Handler to incorporate swipe to delete on my page.
When I press delete, the contact gets deleted however the swipeable remains open.
I want it to close after it gets pressed but I can't seem to figure out how to do it.
This is my code:
const RightActions = (progress, dragX) => {
return (
<TouchableOpacity onPress={()=>{DeleteContact(i)}}>
<View style={[ContactsStyles.rightAction]}>
<Text style={ContactsStyles.actionText}>Delete</Text>
</View>
</TouchableOpacity>
)
}
Here is where I have Swipeable:
<Swipeable renderRightActions={RightActions} >
<View style={ContactsStyles.UserContainer}>
<Text numberOfLines={1} style={[Fonts.Name]}> {obj.firstname} {obj.lastname} </Text>
{/* Message/Call Container */}
<View style={ContactsStyles.ImageCont}>
{/* Message */}
<TouchableOpacity onPress={() => Communications.text(obj.phone, 'Hey ' + obj.firstname + ', im in need of a Ryde. Are you able to pick me up? This is my current location: ' + location)} >
<View style={ContactsStyles.ImageBox}>
<Image style={ContactsStyles.Image} source={require('../../assets/icons/message.png')} />
</View>
</TouchableOpacity>
{/* Call */}
<TouchableOpacity onPress = {() => Communications.phonecall( obj.phone , true)}>
<View style={ContactsStyles.ImageBox}>
<Image style={ContactsStyles.Image} source={require('../../assets/icons/phone.png')} />
</View>
</TouchableOpacity>
</View>
{/* End of Message/Call Container */}
</View>
</Swipeable>

You need to use a reference which has a close method on it.
First define a ref
const swipeableRef = useRef(null);
then assign it to Swipeable
<Swipeable
ref={swipeableRef}
renderLeftActions={renderLeftActions}
onSwipeableLeftOpen={() => handleComplete(item)}
>
...
</Swipeable>
and then just call the close method
const closeSwipeable = () => {
swipeableRef.current.close();
}
Note, for multiple Swipeables you should have multiple refs.
let row: Array<any> = [];
let prevOpenedRow;
renderItem ({ item, index }) {
return (
<Swipeable
ref={ref => row[index] = ref}
friction={2}
leftThreshold={80}
rightThreshold={40}
renderRightActions={renderRightActions}
containerStyle={style.swipeRowStyle}
onSwipeableOpen={closeRow(index)}
...
>
...
</Swipeable>);
}
closeRow(index) {
if (prevOpenedRow && prevOpenedRow !== row[index]) {
prevOpenedRow.close();
}
prevOpenedRow = row[index];
}

It's an index issue . When we have our list keyed on index, and we remove one item (eg index=3), the next item (previously index=4) now adopts the earlier index. Swipeable gets confused by this.
The answer is to key the list on something other than index (eg a unique id).

I had the same problem. What I did to solve that was to set the state array (which is shown in a list) to [] first before deleting that particular item. It cleared-up the screen (including the action panel) and rendered new items.
A part of my code:
const [notes, setNotes] = useState([]);
<FlatList
data={notes}
......
when a note was deleted, I did: setNotes([]) and then setNotes(newNotes)

Related

React Native - IOS - Text strings must be rendered within a <Text> component

Looking at my code for few hours, I was not able to understand the root cause for this exception
Error: Text strings must be rendered within a <Text> component.
This error is located at:
in RCTView (created by View)
in View (created by Card)
in Card (created by StartGameScreen
Can anyone give me a hint? Where do I have to check analyzing? I have a Card component indeed. Is this the first place to check according to the error message?
Card
import {View, StyleSheet} from 'react-native'
import Colors from '../../constants/colors'
function Card({children}) {
return (
<View style = {styles.card}> {children} </View>
)
}
export default Card;
StartGameScreen
function StartGameScreen({onPickNumber}) {
const [enteredNumber, setEnteredNumber] = useState('')
function numberInputHandler(enteredText) {
setEnteredNumber(enteredText)
}
function resetInputHandler() {
setEnteredNumber('')
}
function confirmInputHandler() {
const choseNumber = parseInt(enteredNumber)
if (isNaN(choseNumber) || choseNumber <= 0 || choseNumber > 99) {
Alert.alert('Invalid number', 'Number has to be a number between 1 and 99', [{text:'Okay', style:'destructive', onPress:resetInputHandler}]);
return;
}
onPickNumber(choseNumber);
}
return (
<View style={styles.rootContainer}>
<Title>Guess my number</Title>
<Card>
<InstructionText>dsd</InstructionText>
<View style={styles.textInputContainer}>
<TextInput style={styles.numberInput} maxLength={2} keyboardType="number-pad" autoCapitalize='none' onChangeText={numberInputHandler} value={enteredNumber}/>
</View>
<View style = {styles.buttonsContainer}>
<View style={styles.buttonContainer}>
<PrimaryButton onPress={resetInputHandler}>Reset</PrimaryButton>
</View>
<View style={styles.buttonContainer}>
<PrimaryButton onPress={confirmInputHandler}>Confirm</PrimaryButton>
</View>
</View>
</Card>
</View>
)
}
InstructionText
function InstructionText({children}) {
return (
<Text style={styles.instructionText}>{children}</Text>
)
}
const InstructionText = ({children}) => {
return (
<Text style={styles.instructionText}>{children}</Text>
)
}
This should work !!
if you have spaces in your return statement, you have to wrapping it in parenthesis () and using one line per each tag.

How to use KeyboardAvoidingView with FlatList?

I have a FlatList component with an Input inside each row. When I select the input I want it to scroll up above the keyboard.
My code:
return (
<KeyboardAvoidingView behavior='padding' style={{ flex: 1 }} >
<FlatList
style={{ flex: 1, backgroundColor: '#fff' }}
data={ds}
renderItem={({ item }) => <ListItem data={item} />}
ListFooterComponent={this.renderButton}
/>
</KeyboardAvoidingView>
);
In this scenario, the FlatList is never loaded. When I delete flex:1 from both components, FlatList renders properly but selecting an Input does not make it scroll up
You can trying using react-native-keyboard-aware-scroll-view
https://github.com/APSL/react-native-keyboard-aware-scroll-view
It comes with KeyboardAware[ScrollView, ListView, SectionView, FlatList] which accepts the same props as their corresponding components from RN. I have used that and it worked for me.
render() {
return (
<KeyboardAwareFlatList
style={{flex: 1}}
data={this.state.data}
renderItem={({item}) => (
<View style={{flex: 1}}>
<Image
source={item.v}
style={{height:200, width: 200}}
/>
<TextInput
placeholder="enter text1"
/>
</View>
)}
/>
);
}
You could try using the library react-native-keyboard-spacer as an alternative to KeyboardAvoidingView.
Install:
npm install --save react-native-keyboard-spacer
Use it like this:
import KeyboardSpacer from 'react-native-keyboard-spacer'
...
<View style={{flex: 1}}>
<FlatList
style={{flex: 1}}
data={ds}
renderItem={({ item }) => <ListItem data={item} />}
/>
{/* The view that will expand to match the keyboard height */}
<KeyboardSpacer />
</View>
Try this:
<KeyboardAvoidingView behavior='position' keyboardVerticalOffset={xyz} >
You can remove the property 'keyboardVerticalOffset' or play with the value of xyz,
just find out the better value which fits in your case.
For anyone on a similar path as mine. I was not able to use KeyboardAvoidingView because it depends on ScrollView which conflicts with Flatlist. I couldn't used the header and footer option in Flatlist as I'm using it as a generated thing in a search selection box so it has to be contained.
For me there is a difference in how Android and iOS calculate absolute position. Android considers the bottom to be the top of the keyboard and iOS it is the bottom of the screen when the keyboard is showing.
It turns out to be not that difficult to just put a View around the content you want to remain above the keyboard and just dynamically set the height of it on iOS. This isn't even really necessary on Android as it follows the keyboard if the View is position: absolute and bottom: 0.
This heavily borrows from here: https://stackoverflow.com/a/60682069/438322
Thanks to Kevin Amiranoff
Here's a basic example using hooks.
function YourComponent(props){
const onKeyboardWillShow = e => {
setKeyboardHeight(e.endCoordinates.height);
};
const onKeyboardWillHide = () => {
setKeyboardHeight(0);
};
useEffect(() => {
// These listeners on ios are a little more snappy but not available on Android
// If you want to use this on Android use keyboardDidShow/Hide
if (Platform.OS === 'ios') {
Keyboard.addListener('keyboardWillShow', onKeyboardWillShow);
Keyboard.addListener('keyboardWillHide', onKeyboardWillHide);
}
return () => {
if (Platform.OS === 'ios') {
Keyboard.removeListener('keyboardWillShow', onKeyboardWillShow);
Keyboard.removeListener('keyboardWillHide', onKeyboardWillHide);
}
};
}, []);
const buttonHeight = 50;
return(
<View>
<Content bla={'bla'}/>
<View style={{
height: Platform.OS === 'ios'
? keyboardHeight + buttonHeight : buttonHeight,
position: 'absolute',
bottom: 0
}}>
{/* Keep this button above the keyboard */}
<Button style={{ height: buttonHeight }}/>
</View
</View>
)
}
this is my solution.
inverted={true} is the key
const dummy = [1,2,3,4,5,6,7,8,9,10]
<KeyboardAvoidingView >
<FlatList
data={dummy.reverse()}
inverted={true}
/>
</KeyboardAvoidingView>

How can I use this with FlatList not with ListView?

I took this code from react-native-gifted-chat
and I want to use FlatList
<ListView
enableEmptySections={true}
automaticallyAdjustContentInsets={false}
initialListSize={20}
pageSize={20}
{...this.props.listViewProps}
dataSource={this.state.dataSource}
renderRow={this.renderRow}
renderHeader={this.renderFooter}
renderFooter={this.renderLoadEarlier}
renderScrollComponent={this.renderScrollComponent}
/>
Here's an example for using FlatList to do what you're trying to do:
render() {
return (
<View style={{ flex: 1 }}>
<FlatList
data={someArrayWithDataObjects} // your data source
renderItem={({ item }) => this.renderSearchResults(item)} // how you want each item rendered
keyExtractor={item => item.id} // unique identifier for performance reasons
/>
</View>
);
}

How did I use the push() function in NavigatorIOS?

I want to Push to a new Component by the function push() in NavigatorIOS. It's like following:
renderRow(rowData, sectionID, rowID) {
var imgSource = IMAGE_URLS[rowID];
return (
<TouchableHighlight onPress = {() => {
this.props.navigator.push({
title: 'test',
component: example,
});
}}>
<View>
<View style={styles.row}>
<Image
source={imgSource}
style={styles.thum}
/>
<Text style={styles.text}>
{rowData}
</Text>
</View>
</View>
</TouchableHighlight>
);
}
But it will get a error when I click the TouchableHighlight.
I refered these two questions(1 and 2) before this. And the complete code is in this link
this is not binded to the class inside of renderRow().
You have to bind this either in the constructor:
this.renderRow = this.renderRow.bind(this);
or inside the render method:
render() {
var navStatusBarConfig = {
style: 'light-content',
}
return (
<View style={{ flex: 1, backgroundColor: '#F5FCFF'}}>
<View styles={styles.nav}></View>
<ListView
automaticallyAdjustContentInsets={false}
contentContainerStyle={styles.list}
dataSource={this.state.dataSource}
pageSize={4}
renderRow={this.renderRow.bind(this)}
/>
</View>
);
}
}
As to why, here is the reason :
https://facebook.github.io/react/blog/2015/01/27/react-v0.13.0-beta-1.html#autobinding
And a more complete blog about how to bind this [there are many, blogs and ways to bind this]) :
http://blog.andrewray.me/react-es6-autobinding-and-createclass/

React-native: Why rendering an empty view component creates an error while a text component runs?

I am trying to create an array of View components in react-native that are passed to a parent component. The code below does not work, however if i use Text instead of View it works. I want use view to show empty or filled circles. I couldn't find the reason for it and i assume it should be possible to use an empty view. What am i doing wrong?
class card extends React.Component {
renderScore(balance, fulfill, markImage){
var scores = [];
for (i = 1; i <= fulfill; i++) {
if(i<=balance){
if (markImage) {
scores.push(<View style={styles.emptyCircle} key={i}> </View>);
//scores.push(<View style={styles.emptyCircle} key={i}> <Image source={{uri: markImage}} style={styles.markImage}/> </View>);
}
else{
scores.push(<View style={styles.emptyCircle} key={i}> <View style={styles.punchCircle}></View> </View>);
}
}
else
{
scores.push(<View style={styles.emptyCircle} key={i}> </View>);
}
}
return(scores);
}
}
This will give 'RawText " " must be wrapped in an explicit component.'
However if use
scores.push(<Text style={styles.emptyCircle} key={i}> </Text>);
it works.
It is called from a ListView as
_renderCard(item){
var cardBottom = new cardRenderer();
var childView = cardBottom.renderScore(item.balance, item.fulfill, item.markImage);
console.log(childView);
return(
<View style={styles.mainConatiner}>
<View style={styles.leftConatiner}>
<Image
source={{uri: item.avatarLeft}}
style={styles.thumbnail}
/>
</View>
<View style={styles.rightConatiner}>
<View style={styles.rightUpConatiner}>
<Text style={styles.title}>{item.title}</Text>
<Text style={styles.title}>{item.subTitle}</Text>
<Text style={styles.title}>{item.expires}</Text>
</View>
<View style={styles.rightDownConatiner}>{childView}</View>
</View>
</View>
);
}
Use below for empty view
return(<></>)
try this
return(<View>{scores}</View>);

Resources