There is no transform-origin property in react-native so how can I do it ?
I think I should use transformX & transformY props.
const Marker = () => {
const rotate = new Animated.Value(???);
const transformX = new Animated.Value(???);
const transformY = new Animated.Value(???);
const doRotation = (newDegValue) => {
// ?????
}
return (
<View style={{ width: 200, height: 200, justifyContent: 'center', alignItems: 'center' }}>
<Animated.View transform={[{ rotate }, { transformX }, { transformY }]}>
<ArrowIcon width={30} height={30}>
</Animated.View>
{/* I need to rotate my ArrowIcon around this BaseIcon */}
<BaseIcon width={100} height={100} />
<View/>
)
}
Explanation
In this example, I am using the Animated Library. First, we are defining a Animated Value in the constructor. Then we are creating a triggerAnimation function, where we will animate this new value over time using the timing function.
In the render function, we are defining a new style object called animatedStyle. This style object will handle the rotation of the arrow. We are using the interpolate function to incrementally rotate the arrow. As inputRange we are allowing -1 to +1. -1 means a rotation of -360° and +1 a rotation of 360°-, as you can see in the outputRange.The interpolate function will automatically handle the mapping between input and output range.
In the return statement we are passing the animatedStyle to our <Animated.View>.
Now we can call the triggerAnimation function. As parameter we have to pass the desired rotation value.
Some Examples:
this.triggerAnimation(0.5) would result in a rotation of +180°.
this.triggerAnimation(-0.5) would result in a rotation of -180°.
this.triggerAnimation(0.25) would result in a rotation of +90°.
this.triggerAnimation(0.75) would result in a rotation of +270°.
Code
Constructor and triggerAnimation function:
constructor(props){
super(props);
this.state={
currentRotation: 0,
value: new Animated.Value(0),
}
}
triggerAnimation(newValue){
Animated.timing(this.state.value, {
toValue: newValue,
duration: 500,
}).start(() => {
// set rotated again
this.setState({currentRotation: newValue})
});
}
render function:
render() {
const animatedStyle={
transform: [
{
rotateZ: this.state.value.interpolate({
inputRange: [-1,1],
outputRange: ['-360deg', `360deg`],
}),
},
],
};
return (
<View style={styles.container}>
<View style={{justifyContent: 'center', flexDirection: 'column', alignItems: 'center', width: 150, height: 150}}>
<Animated.View style={[{justifyContent: 'center', flexDirection: 'row', alignItems: 'flex-start'}, StyleSheet.absoluteFill,animatedStyle]} >
<Icon name={"location-arrow"} size={30} style={{transform: [ {rotateZ: '-45deg' }]}}/>
</Animated.View>
<View style={{width: 70, height: 70, borderRadius: 35, backgroundColor: 'blue'}}/>
</View>
<TouchableOpacity onPress={()=>this.triggerAnimation(0.5)} >
<Text> 180° Trigger Animation </Text>
</TouchableOpacity>
...
</View>
);
}
Working Demo
https://snack.expo.io/B1UzO79Cr
You can use
transform: [{ rotate: '40deg' }] in styles
Example: => https://snack.expo.io/#msbot01/mad-cookie
export default class App extends React.Component {
constructor(props){
super(props)
this.state=({
angle:0
})
}
rotate(){
this.setState({
angle: this.state.angle + 5
})
}
render() {
return (
<View style={styles.container}>
<TouchableOpacity onPress={()=>{this.rotate()}} style={{position: "absolute", top:20, width:'100%'}}>
<Text style={{marginTop:20, position:'absolute', top:20}}>Click on me to rotate</Text>
</TouchableOpacity >
<Image style={{height:40, width:40}} source={require('./bus.png')} />
<View onPress={()=>{this.rotate()}} style={{position: "absolute"}}>
<Image style={{height:150, width:150, transform: [{ rotate: (this.state.angle+'deg') }]}} source={require('./ss.png')} />
</View >
</View>
);
}
}
Related
I have an image array where I am updating each value when I click on a button in react and pick an image. However the array is constantly rerendering when I only want it to when I pick an image.
Here is my code:
function OnboardingUploadPhotos() {
const [modalVisible, setModalVisible] = useState(false);
const [permissionStatus, setPermissionStatus] = useState('');
const [files, setFiles] = useState<string[]>([]);
const [fileArray, setFileArray] = useState<string[]>(['', '', '', '', '', '', '']);
const setPermissions = useCallback(val => {
setPermissionStatus(val);
}, [setPermissionStatus]);
const setIsModalVisible = useCallback(val => {
setModalVisible(val);
}, [setModalVisible]);
async function fetchAssets() {
const recentCameraRoll = await MediaLibrary.getAssetsAsync({first: 11});
setFiles(recentCameraRoll.assets.slice(1).map(file => file.uri));
}
function replaceFileState(file: string, index: number) {
let f = [...fileArray];
f[index] = file;
setFileArray(f);
}
useEffect(() => {
fetchAssets();
}, [fetchAssets]);
return (
<View>
{permissionStatus ?
<Modal style={styles.bottomModalView} isVisible={modalVisible} backdropOpacity={0}
onBackdropPress={() => setModalVisible(false)}>
<View style={styles.modal}>
<TouchableOpacity>
<Text style={{
borderBottomWidth: 1,
borderBottomColor: '#FFF',
color: '#FFF',
textDecorationLine: 'underline',
alignSelf: 'flex-end',
justifyContent: 'center',
paddingTop: 40
}}>All photos</Text>
</TouchableOpacity>
<ScrollView horizontal={true} scrollEnabled={true}
contentContainerStyle={{justifyContent: 'center', alignItems: 'center'}}>
{files.map((file, index) => {
return (
<TouchableWithoutFeedback key={index} onPress={() => replaceFileState(file, index)}>
<Image
key={file}
style={{width: 100, height: 100, marginLeft: 10, marginRight: 10, borderRadius: 4}}
source={{uri: file}}
/>
</TouchableWithoutFeedback>
);
})}
</ScrollView>
<Text style={{fontSize: 22, color: '#FFF', marginLeft: 20, marginBottom: 20}}>Your photos</Text>
</View>
</Modal> :
null
}
<Text>Upload your photos</Text>
<View style={{flexDirection: "row", flexWrap: "wrap", justifyContent: 'space-evenly'}}>
{fileArray.slice(0,6).map((image, index) => {
console.log(fileArray)
return (
image != '' ?
<Image
key={image}
style={{width: 100, height: 100, borderRadius: 100}}
source={{uri: image}}
/> :
<OnboardingPhoto key={index} setStatus={setPermissions} setModal={setIsModalVisible}/>
)
})}
</View>
</View>
);
}
It is a modal that has all the users latest images and on click of each image it replaces the empty placeholder image with the users image on the screen. However it constantly rerenders. Id say put it in a useEffect but I do not know where! Any help would be great, thanks!
I think you need to define a key property on the TouchableWithoutFeedback component.
See the guidelines here: https://reactjs.org/docs/lists-and-keys.html
Hooks are ment to be used inside Function Components, that's maybe one of the reasons.
I have a TextInput that when pressed gets covered by the keyboard. So I wrapped it in a KeyboardAvoidingView. But regardless of the behavior that I set for this view, the TextInput won't move above the keyboard. Using position as the behavior moves the TextInput but only half way above the keyboard, while the other two don't seem to work at all.
I also tried wrapping my entire component with a KeyboardAvoidingView, but doing so breaks the entire layout.
Can anyone help me? I never managed to get KeyboardAvoidingView to work for me and now I really need it. Thanks in advance!
Here is my component. Also worth mentioning is that this component is top level(well, almost top level since it's wrapped in a Router)
const { height, width } = Dimensions.get('screen')
const style = StyleSheet.create({
main: {
height,
width,
flexDirection: 'column',
},
iconSelecter: {
width,
height: 196,
alignItems: 'center',
justifyContent: 'center',
backgroundColor: Colors.primary,
marginTop: 32
},
icon: {
height: 164,
width: 164,
},
saveButton: {
width: 96,
height: 96,
borderRadius: 100,
backgroundColor: Colors.secondary,
alignItems: "center",
justifyContent: "center",
alignSelf: 'center',
position: 'absolute',
bottom: 96 + 32
},
saveIcon: {
height: 54,
width: 54,
},
textInputWrapper: {
borderBottomColor: Colors.textInputBorder,
width: 288,
borderBottomWidth: 1,
alignSelf: 'center',
marginTop: 96,
height: 48,
},
textInput: {
fontWeight: "300",
fontSize: 14,
margin: 0
},
hintWrapper: {
alignSelf: 'center',
marginTop: 4
},
hint: {
fontSize: 12,
fontFamily: "Roboto-Thin",
fontStyle: 'normal',
}
})
const CreateActivity = ({ goBack }: NavigationProps) => {
//////////////////////////////
//State and logic
///////////////
return (
// TODO: Add touchable opacity to dismiss keyboard
<View style={style.main}>
<Appbar title="New activity" canGoBack goBack={goBack} />
<View style={{ flex: 1 }}>
<View style={style.iconSelecter}>
<GestureRecognizer onSwipeLeft={nextIcon} onSwipeRight={lastIcon}>
<Image style={style.icon} source={icons[currentIconIndex]?.file}></Image>
</GestureRecognizer>
</View>
<View style={style.hintWrapper}>
<Text style={style.hint}>Swipe to cycle through the icons</Text>
</View>
<KeyboardAvoidingView>
<View style={style.textInputWrapper}>
<TextInput style={style.textInput} placeholder={"Give this activity a name"} value={name} onChangeText={setName}></TextInput>
</View>
</KeyboardAvoidingView>
<TouchableNativeFeedback onPress={createActivity} background={TouchableNativeFeedback.Ripple("#fff", true)}>
<View style={style.saveButton}>
<Image style={style.saveIcon} source={require("../../assets/icons/light/save.png")}></Image>
</View>
</TouchableNativeFeedback>
</View>
</View>
)
}
export default CreateActivity;
I suggest that you to try wrap all the content of the screen in <KeyboardAvoidingView /> (or make it one of the outermost elements), otherwise it only will slide up its children (the View and the TextInput) leaving the rest of the content in its original position, making the layout look overlaped and weird. If you do that, the value "position" should work fine.
Something like this:
<View style={style.main}>
<Appbar title="New activity" canGoBack goBack={goBack} />
<KeyboardAvoidingView behavior="position" >
<View style={{ flex: 1 }}> // --> Remove flex: 1 if you experience some issue with the positioning
<View style={style.iconSelecter}>
<GestureRecognizer onSwipeLeft={nextIcon} onSwipeRight={lastIcon}>
<Image style={style.icon} source={icons[currentIconIndex]?.file}></Image>
</GestureRecognizer>
</View>
<View style={style.hintWrapper}>
<Text style={style.hint}>Swipe to cycle through the icons</Text>
</View>
<KeyboardAvoidingView>
<View style={style.textInputWrapper}>
<TextInput style={style.textInput} placeholder={"Give this activity a name"} value={name} onChangeText={setName}></TextInput>
</View>
</KeyboardAvoidingView>
<TouchableNativeFeedback onPress={createActivity} background={TouchableNativeFeedback.Ripple("#fff", true)}>
<View style={style.saveButton}>
<Image style={style.saveIcon} source={require("../../assets/icons/light/save.png")}></Image>
</View>
</TouchableNativeFeedback>
</View>
</KeyboardAvoidingView>
</View>
Also see the comment in the code above. Check if you really need to use of flex: 1 in all the outer wrapper elements, and take a look to the height you are setting in the style.main based on dimentions. I don't think that it is necesary and I think it could lead to some measure issues if you fix the height of the parent container.
EDIT:
I was just digging in react-native docs and I realize that there is a zIndex that you could use to avoid ablsolute positioning. It is a relative style prop so it needs to be set between sibling views, like this:
export default class MyComponent extends React.Component {
render() {
return (
<View>
<View style={[styles.appbarShape, styles.appbarZIndex]} ><Text>Header</Text></View>
<KeyboardAvoidingView behavior="position" style={styles.contentZIndex}>
{other children}
<TextInput placeholder="enter text"/>
</KeyboardAvoidingView>
</View>
);
}
}
const styles = StyleSheet.create({
appbarShape: {
height: 80,
width: Dimensions.get('window').width,
justifyContent: 'center',
alignSelf: "stretch",
backgroundColor: "#FFF"
},
appbarZIndex: {
zIndex: 3,
},
contentZIndex: {
zIndex: 0
}
});
Since the view that represents the appbar has a greater zIndex it shows up over the ones with a lower zIndex
Check this out working in this snack https://snack.expo.io/5VXAcw4Y0
Docs: https://reactnative.dev/docs/layout-props
Hope it helps!
Use react-native-keyboard-aware-scroll-view
<KeyboardAwareScrollView extraHeight={135} enabledOnAndroid={true}
extraScrollHeight={70} style={styles.mainContainer}
automaticallyAdjustContentInsets={true}
enableOnAndroid={true}
keyboardShouldPersistTaps='handled'
scrollEnabled={true} >
//your form
</KeyboardAwareScrollView>
const styles = StyleSheet.create({
mainContainer: { flex: 1, marginHorizontal: 15, marginVertical: 15 },
});
I am a newbie to react native, I want to make this layout possible I have following code but it puts the logo inside grid
What I am looking for is this
import React, { Component } from 'react';
import GridView from 'react-native-super-grid';
export default class ProfileScreen extends Component {
static navigationOptions = {
title: 'Details',
};
render() {
const { navigate } = this.props.navigation;
const items = [
{ name: require('./images/shopping-cart.png'),code: '#2ecc71' }, { name: require('./images/home.png'), code: '#2ecc71' },
{ name: require('./images/money-bag.png'), code: '#2ecc71' }, { name: require('./images/alert.png'), code: '#2ecc71' }
];
return (
<ImageBackground
source={require('./images/marble.jpg')}
style={styles.backgroundImage}>
<View style={styles.mainLayout}>
<Image resizeMode={'cover'} style = {styles.logoFit} source={require('./images/Logo1.png')}/>
<GridView
itemDimension={130}
items={items}
style={styles.gridView}
renderItem={item => (
<View style={styles.itemContainer}>
<View style={styles.CircleShapeView}>
<Image style={styles.iconItem} source={item.name}/>
</View>
</View>
)}
/>
</View>
</ImageBackground>
);
}
}
const dimensions = Dimensions.get('window');
const imageHeight = Math.round(dimensions.width * 9 / 16);
const imageWidth = dimensions.width;
const styles = StyleSheet.create({
backgroundImage: {
flex: 1,
resizeMode: 'cover', // or 'stretch'
},
CircleShapeView: {
width: 100,
height: 100,
borderRadius: 100,
backgroundColor: '#00BCD4',
justifyContent: 'center',
alignItems: 'center'
},
gridView: {
paddingTop: 50,
flex: 1,
},
itemContainer: {
justifyContent: 'center',
alignItems:'center',
height:130
},
iconItem: {
alignItems:'center',
justifyContent: 'center'
},
logoFit: {
width: imageHeight,
height: imageWidth
},
mainLayout: {
flex: 1,
flexDirection: 'column',
justifyContent: 'space-between'
}
});
Get rid of that grid component. You don't need it for such a simple thing. It's complicating things, and as it's not a regular/common component we don't know how it's affecting things.
This looks quite simple:
<View>
<View style={{}}>
<Image />
</View>
<View style={{flexDirection:'row'}}>
<View>
<Text>row 1, col 1</Text>
</View>
<View>
<Text>row 1, col2Text>
</View>
</View>
<View style={{flexDirection:'row'}}>
<View>
<Text>row 2, col 1</Text>
</View>
<View>
<Text>row 2, col2Text>
</View>
</View>
<View style={{}}>
<Button title="Login" />
</View>
</View>
Here's another similar question - How to create 3x3 grid menu in react native without 3rd party lib?
Inside navigationOptions You should remove the title property and define a header property and put your Image there. Like this
static navigationOptions = {
header:(<Image resizeMode={'cover'} style = {styles.logoFit} source={require('./images/Logo1.png')}/>)
};
Or... YOu can just make the header null as
static navigationOptions = {
header:null
};
and your current code would work as you want it to be.
I want to move the label with respect to slider thumb just like this one:
Right now my slider is like this:
I want to display the label shown as 30 km with respect to the slider thumb such that as the slider moves, the label should move accordingly.
I am using Native React Slider component.
this is my code:
<Slider
style={styles.slider}
thumbTintColor='rgb(252, 228, 149)'
step={1}
maximumValue={5}
thumbTintColor='rgb(252, 228, 149)'
maximumTrackTintColor='#494A48'
minimumTrackTintColor='rgb(252, 228, 149)' />
You can adjust left of the text to the value of the slider.
const left = this.state.value * (screenWidth-60)/100 - 15;
<Text style={ { width: 50, textAlign: 'center', left: left } }>
{Math.floor( this.state.value )}
</Text>
<Slider maximumValue={100}
value={this.state.value}
onValueChange={value => this.setState({ value })} />
Solution to your problem:
constructor(props){
super(props)
this.state = {
distance: 30,
minDistance: 10,
maxDistance: 100
}
}
render() {
return (
<View style={styles.container}>
<Slider
style={{ width: 300}}
step={1}
minimumValue={this.state.minDistance}
maximumValue={this.state.maxDistance}
value={this.state.distance}
onValueChange={val => this.setState({ distance: val })}
thumbTintColor='rgb(252, 228, 149)'
maximumTrackTintColor='#d3d3d3'
minimumTrackTintColor='rgb(252, 228, 149)'
/>
<View style={styles.textCon}>
<Text style={styles.colorGrey}>{this.state.minDistance} km</Text>
<Text style={styles.colorYellow}>
{this.state.distance + 'km'}
</Text>
<Text style={styles.colorGrey}>{this.state.maxDistance} km</Text>
</View>
</View>
);
}
}
Styles:
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#000',
},
textCon: {
width: 320,
flexDirection: 'row',
justifyContent: 'space-between'
},
colorGrey: {
color: '#d3d3d3'
},
colorYellow: {
color: 'rgb(252, 228, 149)'
}
});
Output:
Working Snippet:
https://snack.expo.io/Syrt3Bs7z
Built in <Slider /> doesn't provide those flexibility to customize what you want.
This should works, react-native-slider, which is a drop-in replacement of official <Slider />.
What you need is very similar to it's demo style #4.
For your Slider Label of value, you can modify its function _renderThumbImage() to replace default <Image />.
measure the size and position of the slider View
<Slider
maximumValue={10}
onLayout={(event)=>{this.slider_bound(event)}}/>
//majore size of Slider.
slider_bound=(event)=>{
var {x, y, width, height} = event.nativeEvent.layout;
this.state.slider_Width=width;
this.state.slider_Height=height;
this.state.slider_x = x;
this.state.slider_y = y;
this.state.slider_x_step_size = width/10; //Devide the width by slider maximum value
this.setState({triger:''});
console.log(TAG+"Slider Dimen:"+width,height+'pos:',x,y);
}
2.Now in "onValueChange" callback of slider.
//compute position to show slider value txt
this.state.value_x = (value * this.state.slider_x_step_size) + this.state.slider_x;
this.state.value_y = this.state.slider_Height + this.state.slider_y;
Show the slider Value txt on Calculated positions.
<Text style={{position:'absolute',top:this.state.value_y,left:this.state.value_x,color:colors.blackc}}>{this.state.data.slider_value}</Text>
..............
that will do the job, but You might have to tweak it little bit.
I think react-native-multi-slider will solve your problem . You can change the slider thumb by sending your custom designed component to the customMarker prop that is available. Then you can wrap the Multislider in another component, maintain the state(for slider position value) there and send value as prop to your custom designed marker everytime the thumb position changes which can be detected using onValuesChange prop.
This link might also help you.
import {Slider} from 'react-native-elements';
<Slider
thumbStyle={{height: 15, width: 15, backgroundColor: 'orange'}}
maximumTrackTintColor="grey"
minimumTrackTintColor="orange"
thumbProps={{
children: (
<View
style={{
color: 'green',
marginTop: -22,
width: 100,
}}>
<Text>Text</Text>
</View>
),
}}
/>
I am creating list of items on react native app. I want to make swipe to left and right every item of this list. So when I swipe to left I want to hide Views that was rendered and show new elements. Also when I swipe to right I want to display other elements that will be different from that element that will be rendered when I swipe left. I found this library called "react-native-swipe-gestures" but I can't figure out how to display and hide elements with it. I declared some items but when I try to display it i got an error that "can't find variable 'item'" maybe you will have some explain to me how I should use it to actually get working swipe left and right.
import React, {Component} from 'react';
import {
ScrollView,
Text,
View,
Image,
Button
} from 'react-native';
import GestureRecognizer, {swipeDirections} from 'react-native-swipe-gestures';
import {List, ListItem} from "react-native-elements";
class Offers extends Component {
constructor(props) {
super(props);
this.state = {
myText: '',
gestureName: 'none',
icons: '',
guardian: '',
area: '',
rooms: '',
floor: '',
market: '',
year: '',
pricePerMeter: '',
};
}
onSwipeRight(gestureState) {
this.setState({myText: 'You swiped right!'});
}
onSwipe(gestureName, gestureState) {
const {SWIPE_LEFT, SWIPE_RIGHT} = swipeDirections;
this.setState({gestureName: gestureName});
switch (gestureName) {
case SWIPE_LEFT:
this.setState({backgroundColor: 'blue'});
break;
case SWIPE_RIGHT:
this.setState({backgroundColor: 'yellow'});
break;
}
}
onSwipeLeft(gestureState) {
this.setState({
guardian: item.offerGuardian, //items from const offers
area: item.offerArea,
floor: item.offerFloor,
rooms: item.offerRooms,
market: item.offerMarket,
year: item.offerYear,
pricePerMeter: item.offerPricePerMeter,
})
}
render() {
const config = {
velocityThreshold: 0.3,
directionalOffsetThreshold: 80
};
const offers = [
{
offerNumber: 'TEST912697',
offerLocation: 'Warszawa Białołęka',
offerStreet: 'ul. Bruszewska',
offerType: 'Mieszkanie',
offerStatus: 'Akt. Wewnętrzna',
offerStatusColor: '#0FBEB2',
offerAddDate: '2017-09-20 12:08:06',
offerPrice: '2 450 000',
photo: 'https://static.pexels.com/photos/164516/pexels-photo-164516.jpeg',
offerGuardian: 'Adam Borek',
offerTransactionType: 'sprzedaż',
offerArea: '50 m2',
offerRooms: '2 pokoje',
offerFloor: '1 z 2',
offerYear: '2005 rok',
offerMarket: 'rynek pierwotny',
offerPricePerMeter: '5000 zł/m2'
},
];
return (
<ScrollView>
<View style={{
flexDirection: 'row',
justifyContent: 'flex-end',
alignItems: 'flex-end'
}}>
<View style={{marginRight: 20, marginTop: 10}}>
<Button title="akcje"/>
</View>
</View>
{offers.map((item, i) => (
<View key={i}>
<List>
<GestureRecognizer
onSwipe={(direction, state) => this.onSwipe(direction, state)}
onSwipeLeft={(state) => this.onSwipeLeft(state)}
onSwipeRight={(state) => this.onSwipeRight(state)}
config={config}
style={{
flex: 1,
backgroundColor: this.state.backgroundColor
}}
>
<ListItem
roundAvatar
subtitle={
<View style={{flexDirection: 'row'}}>
<View>
<Text>{this.state.myText}</Text>
<Text>{this.state.guardian}</Text>
<Text>{this.state.area}</Text>
<Text>{this.state.floor}</Text>
<Text>{this.state.market}</Text>
<Text>{this.state.year}</Text>
<Text>{this.state.pricePerMeter}</Text>
<Image source={require('../../gfx/lel.jpg')}
style={{
height: 100,
width: 150
}}/>
</View>
<View style={{
marginLeft: 5,
flexDirection: 'row',
flexWrap: 'wrap'
}}>
<View style={{width: 140}}>
<Text>
{item.offerLocation}
{"\n"}
{item.offerStreet}
{"\n"}
{item.offerType} na {item.offerTransactionType}
{"\n"}
{item.offerNumber}
</Text>
</View>
<View>
<View style={{
justifyContent: 'flex-end',
width: 95,
height: 30,
backgroundColor: item.offerStatusColor
}}>
<Text style={{color: '#fff', textAlign: 'center'}}>
{item.offerStatus}
</Text>
</View>
<View style={{
flexDirection: 'column',
alignItems: 'flex-end',
flexWrap: 'wrap'
}}>
<Text style={{fontSize: 20}}>
{"\n"}
{"\n"}
{item.offerPrice}
</Text>
</View>
</View>
</View>
</View>
}
onPress={() => this.props.navigation.navigate('OffersDetails')}
/>
</GestureRecognizer>
</List>
</View>
))}
</ScrollView>
)
}
}
export default Offers;
I would recommend trying out this libray react-native-swipe-list-view. It is well documented and easy to use for any type of swipable row. https://github.com/jemise111/react-native-swipe-list-view
The basic concept is that you have one element in front of a hidden element. When you swipe it just reveals the hidden element below.