react native: rendering cards that flip on user tap - reactjs

I'm working on a React Native code that renders “cards” on an array using a map function. Each card is wrapped in a touchableOpacity component, so that when a user taps the card, it would flip. Currently the issue is that, if a user taps to flip one card, all neighboring cards flip as well. I would like to have the flip functionality for each card be independent. When a card is flipped it should not trigger the flipping of neighboring cards as well. Thanks in advance to reading this.
class SavedBooks extends Component {
componentWillMount() {
this.animatedValue = new Animated.Value(0);
this.value = 0;
this.animatedValue.addListener(({ value }) => { this.value = value })
}
frontCardStyle() {
this.frontInterpolate = this.animatedValue.interpolate({
inputRange: [0, 180],
outputRange: ['0deg', '180deg']
})
const frontAnimatedStyle = {
transform: [ { rotateY: this.frontInterpolate }]
}
return frontAnimatedStyle
}
backCardStyle() {
this.backInterpolate = this.animatedValue.interpolate({
inputRange: [0, 180],
outputRange: ['180deg', '360deg']
})
const backAnimatedStyle = { transform: [{ rotateY: this.backInterpolate }] }
return backAnimatedStyle
}
flipCard() {
if (this.value >= 90) {
Animated.spring(this.animatedValue, {
toValue: 0,
friction: 8,
tension: 10
}).start();
} else if (this.value < 90) {
Animated.spring(this.animatedValue, {
toValue: 180,
friction: 8,
tension: 10
}).start();
}
}
renderElemets(color) {
const { savedBooks } = this.props.book
return savedBooks.map((book, index) => {
return (
<View
key={index}
style={{ alignItems: 'center' }}>
<TouchableOpacity onPress={() => this.flipCard()} >
<Text
style={{ fontFamily: 'Helvetica',
fontSize: 25,
padding: 15 }}>
{book.title}
</Text>
<Animated.View>
<Animated.Image
style={[this.frontCardStyle(), styles.cardStyle]}
source={{ uri: book.image }}
/>
<Animated.View style={[this.backCardStyle(), styles.cardStyle, styles.flipCardBack]}>
<Text>{book.description}</Text>
</Animated.View>
</Animated.View>
</TouchableOpacity>
</View>
)
});
}
render() {
return (
<ScrollView>
{this.renderElemets(color)}
</ScrollView>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#FFF',
},
imageStyle: {
position: 'absolute',
top: 0,
left: 0,
bottom: 0,
right: 0,
},
cardStyle: {
height: 400,
width: 250,
backfaceVisibility: 'hidden',
},
flipCardBack: {
position: "absolute",
top: 0,
},
});

Because all the cards share the same style. You should create a card component so each card can have their own style and the press event won't affect other cards.

I had this exact problem and solved it by adding something like an identifier or tag to the clicked item in the array. The idea is quite similar to adding a class 'active' to the item clicked in the array, that way your flipCard() function will only be run on the item which has been labelled active. In short, only add the frontCardStyle() or backCardStyle() to the item that has been clicked and labelled 'active'. That way only the active item will flip.
I followed this example and came up with the solution below;
constructor(props) {
super(props);
this.state = {
activeItem: {},
}
this.toggleActiveItem = this.toggleActiveItem.bind(this);
}
toggleActiveItem(index) {
this.setState({
activeItem: {
[index]: true
}
});
this.flipCard();
}
renderElemets(color) {
const { savedBooks } = this.props.book
return savedBooks.map((book, index) => {
return (
<View
key={index}
style={{ alignItems: 'center' }}>
<TouchableOpacity onPress={() => this.toggleActiveItem(index)} >
<Text
style={{ fontFamily: 'Helvetica',
fontSize: 25,
padding: 15 }}>
{book.title}
</Text>
<Animated.View>
<Animated.Image
style={[this.state.activeItem[index] && this.frontCardStyle(), styles.cardStyle]}
source={{ uri: book.image }}
/>
<Animated.View style={[this.state.activeItem[index] && this.backCardStyle(), styles.cardStyle, styles.flipCardBack]}>
<Text>{book.description}</Text>
</Animated.View>
</Animated.View>
</TouchableOpacity>
</View>
)
});
}

Related

Navigating to another screen while passing state to a button

So I originally needed to pass states while navigating from a screen to another because I thought that would be enough to update a button as well as the screen iteself. However, the button that controlled the states is not being updated with the screen.
In the demo provided below(I included the code here as well) you can see that when navigating from Screen3, the state updates so that the red screen renders but the button at the top does not update as well.
How can I update the button along with the screen being updated?
I need to go from screen3 to the red screen while the button at the top shows we are on the red screen as well.
Here is the demo as well as the code below. Please keep in mind you must run the snack on IOS or android and you need to be running Expo Version 42(located in the bottom right of the screen)
Thank you for any insight at all! I appreciate it more than you know.
Home.js
import Slider from './components/slider'
const Home = ({ route }) => {
const [isVisile, setIsVisible] = React.useState(true);
const [whichComponentToShow, setComponentToShow] = React.useState("Screen1");
React.useEffect(() => {
if(route.params && route.params.componentToShow) {
setComponentToShow(route.params.componentToShow);
}
}, [route.params]);
const goToMap = () => {
setComponentToShow("Screen2");
}
const goToList = () => {
setComponentToShow("Screen1");
}
return(
<View style={{backgroundColor: '#d1cfcf' ,flex: 1}}>
{whichComponentToShow === 'Screen1' && <ListHome />}
{whichComponentToShow === 'Screen2' && <MapHome />}
<View style={{position: 'absolute', top: 0, left: 0, right: 1}}>
<Slider
renderMap={goToMap}
renderList={goToList}
/>
</View>
</View>
);
}
Screen3.js
const Screen3 = (props) => {
const navigation = useNavigation();
const onPress = () => {
navigation.navigate('Home', {
screen: 'Home',
params: {
componentToShow: 'Screen2'
}
});
}
return (
<View
style={{
flex: 1,
backgroundColor: 'white',
justifyContent: 'center',
alignItems: 'center',
}}>
<TouchableOpacity onPress={onPress}>
<Text style={{ color: 'black', fontSize: 25 }}>
Navigate to home and change to map screen
</Text>
</TouchableOpacity>
</View>
);
};
Finally Slider.js
const Slider = (props) => {
const [active, setActive] = useState(false)
let transformX = useRef(new Animated.Value(0)).current;
useEffect(() => {
if (active) {
Animated.timing(transformX, {
toValue: 1,
duration: 300,
useNativeDriver: true
}).start()
} else {
Animated.timing(transformX, {
toValue: 0,
duration: 300,
useNativeDriver: true
}).start()
}
}, [active]);
const rotationX = transformX.interpolate({
inputRange: [0, 1],
outputRange: [2, Dimensions.get('screen').width / 4]
})
return (
code for animation
)
Your Slider component needs to listen to screen focus with useFocusEffect and react accordingly. I added new props active to detect if the map screen is active.
Slider.js
import * as React from 'react';
import { useState, useEffect, useRef } from 'react'
import { View, Text, StyleSheet, Animated, TouchableOpacity, SafeAreaView, Dimensions, } from 'react-native';
import {
scale,
verticalScale,
moderateScale,
ScaledSheet,
} from 'react-native-size-matters';
import { useFocusEffect } from '#react-navigation/native';
const Slider = (props) => {
const [active, setActive] = useState(false)
let transformX = useRef(new Animated.Value(0)).current;
useFocusEffect( React.useCallback(()=>{
setActive(Boolean(props.active))
console.log()
},[props.active]))
useEffect(() => {
if (active) {
Animated.timing(transformX, {
toValue: 1,
duration: 300,
useNativeDriver: true
}).start()
} else {
Animated.timing(transformX, {
toValue: 0,
duration: 300,
useNativeDriver: true
}).start()
}
}, [active]);
const rotationX = transformX.interpolate({
inputRange: [0, 1],
outputRange: [2, Dimensions.get('screen').width / 4]
})
return (
<SafeAreaView style={{
alignItems: 'center',
backgroundColor:'transparent'
}}>
<View style={{
flexDirection: 'row',
position: 'relative',
height: 45,
width: 240,
borderRadius: 10,
backgroundColor: 'white',
marginHorizontal: 5
}}>
<Animated.View
style={{
position: 'absolute',
height: 45 - 2*2,
top: 2,
bottom: 2,
borderRadius: 10,
width: Dimensions
.get('screen').width / 3 - 3.5 ,
transform: [
{
translateX: rotationX
}
],
backgroundColor: '#d1cfcf',
}}
>
</Animated.View>
<TouchableOpacity style={{
flex: 1,
justifyContent: 'center',
alignItems: 'center'
}} onPress={() => {setActive(false); props.renderList() }}>
<Text>
List
</Text>
</TouchableOpacity>
<TouchableOpacity style={{
flex: 1,
justifyContent: 'center',
alignItems: 'center'
}} onPress={() => {setActive(true); props.renderMap() }}>
<Text>
Map
</Text>
</TouchableOpacity>
</View>
</SafeAreaView>
);
}
export default Slider
I update Slider Component in Home.js as below.
<Slider
renderMap={goToMap}
renderList={goToList}
active={route.params && route.params.componentToShow==='Screen2'}
/>
Check full working snack:
https://snack.expo.dev/#emmbyiringiro/283f93

How to do curve like animation in react native?

I know that the title is not very specific, i'm extremely sorry for that, actually I couldn't think a better title than it.
Now coming to my design, Below is the desired result. I want to do it with an CURVED like animation (just like the image). But the problem is its going like a straight diagonal line not curved one.
_startCoin function is called whenever i click a button and then animation is happening
And here is my code
const screenHeight = Math.round(Dimensions.get('window').height);
const screenWidth = Math.round(Dimensions.get('window').width);
export default class demoScreen extends Component {
state = {
ready: false,
CoinAdd: new Animated.Value(0),
};
_startCoin = () => {
return Animated.parallel([
Animated.timing(this.state.CoinAdd, {
toValue: 1,
duration: 1500,
useNativeDriver: true
})
]).start();
};
render() {
let { CoinAdd } = this.state;
return (
<View style={styles.container}>
<TouchableOpacity style={styles.btn2} onPress={() => this._startCoin()}>
<Text style={styles.textBtn}>Start Coin</Text>
</TouchableOpacity>
<Animated.View style={{
transform: [
{
translateY: CoinAdd.interpolate({
inputRange: [0, 1],
outputRange: [0, -((screenHeight/2)-50)]
})
},
{
translateX: CoinAdd.interpolate({
inputRange: [0, 1],
outputRange: [0, -((screenWidth/2))]
})
}
],
borderRadius: 12,
justifyContent: "center",
alignItems: 'center',
zIndex: 2
}}
>
<Image style={{position: "absolute", height: 30, width: 30, right: 0}} source={require('../../assets/coin.png')} />
</Animated.View>
);
}
}
Any kind of help would be appreciated. Thank you

How to select only one item from a flatlist in React and change it's style?

I'm stuck with trying to make a flatlist work with one selection and change only it's background. I already got the id from the pressed item and I'm passing the information to another page. But after clicking, it's style is not changing. I need to select just one and if I click on another it should deselect the first one and keep the new one selected.
My code is as folows:
import React, { Component } from 'react';
import { StyleSheet, Text, View, Image, TouchableOpacity, FlatList, Linking, ActivityIndicator } from 'react-native';
import { Searchbar } from 'react-native-paper';
export default class Merchants extends Component {
constructor() {
super()
this.state = {
search: '',
loading: false,
merchantObj: [],
btnDisabled: true,
itemId: null,
imgLink: null,
listClicked: false,
}
this.arrayholder = [];
}
componentDidMount() {
const merchantUrl = 'http://165.227.43.115:8080/merchant/merchant'
fetch(merchantUrl)
.then(response => response.json())
.then(data => {
this.setState({ merchantObj: data, loading: false },
function () {
this.arrayholder = data;
})
})
.catch(error => {
console.log(error)
});
}
search = text => {
console.log(text);
};
clear = () => {
this.search.clear();
};
SearchFilterFunction(text) {
//passing the inserted text in textinput
const newData = this.arrayholder.filter(function (item) {
//applying filter for the inserted text in search bar
const itemData = item.name ? item.name.toUpperCase() : ''.toUpperCase();
const textData = text.toUpperCase();
return itemData.indexOf(textData) > -1;
});
this.setState({
//setting the filtered newData on datasource
//After setting the data it will automatically re-render the view
merchantObj: newData,
search: text,
});
}
FlatListItemSeparator = () => {
return (
<View
style={{
height: 1,
width: "95%",
justifyContent: 'center',
backgroundColor: "#DCDCDC",
}}
/>
);
}
MerchSelected = (selectedId) => {
if (this.state.btnDisabled === true) {
return (
<View style={styles.btnDsb}>
<Text style={styles.txtBtn}>Select</Text>
</View>
)
} else {
return (
<TouchableOpacity onPress={(item) => this.props.navigation.navigate('Main', { itemId: this.state.itemId, itemImg: this.state.imgLink })}>
<View style={styles.btnSelect}>
<Text style={styles.txtBtn}>Select</Text>
</View>
</TouchableOpacity>
)
}
}
PressedItem = (itemId, itemImg) => {
console.log(itemId)
this.setState({ itemId: itemId, btnDisabled: false, imgLink: itemImg })
}
renderItem = ({ item }) => {
return (
<TouchableOpacity onPress={() => this.PressedItem(item.id, item.image)} >
<View style={styles.listItem} >
<Image
style={{ width: 80, height: 80 }}
source={{ uri: `${item.image}` }} />
<View style={{ flexDirection: 'column', marginLeft: 2 }}>
< Text style={{ fontWeight: 'bold', fontSize: 20 }} > {item.name} </Text>
{item.shoppingOption == 'STORE' ? <Text>Store</Text> : <Text>In-Store & Online</Text>}
<Text>${item.minAmount} - ${item.maxAmount}</Text>
<Text style={{ color: '#00CED1' }}
onPress={() => Linking.openURL(`${item.website}`)}>
view website
</Text>
</View>
</View>
</TouchableOpacity>
)
}
render() {
if (this.state.loading) {
return (
<View>
<Text>Loading...</Text>
</View>
);
}
return (
<View style={styles.container} >
<View style={styles.searchBar}>
<Searchbar
round
placeholder="Search"
onChangeText={text => this.SearchFilterFunction(text)}
onClear={text => this.SearchFilterFunction('')}
value={this.state.search}
/>
</View>
<View style={styles.merchantsList}>
<FlatList
data={this.state.merchantObj}
renderItem={this.renderItem}
ItemSeparatorComponent={this.FlatListItemSeparator}
keyExtractor={item => item.id.toString()}
extraData={this.state}
>
</FlatList>
</View>
<View style={styles.footerBtn}>
<TouchableOpacity onPress={() => this.props.navigation.navigate('Main', { itemId: undefined })}>
<View style={styles.btnSelect}>
<Text style={styles.txtBtn}>Cancel</Text>
</View>
</TouchableOpacity>
{this.state.btnDisabled === true ? this.MerchSelected('Sim') : this.MerchSelected('Nao')}
</View>
</View >
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#f2f2f4',
alignItems: 'center',
},
searchBar: {
flex: 1,
top: '5%',
width: '90%',
backgroundColor: 'rgba(242, 242, 244,0.5)'
},
merchantsList: {
flex: 6,
width: '95%',
},
footerBtn: {
flex: 1,
width: '100%',
},
listItem: {
flexDirection: 'row',
marginTop: 5,
},
notSelected: {
backgroundColor: '#f2f2f4'
},
listItemSlc: {
backgroundColor: '#48D1CC',
},
btnSelect: {
justifyContent: 'center',
width: '95%',
borderRadius: 5,
borderColor: '#00CED1',
borderStyle: 'solid',
borderWidth: 2,
height: 40,
marginTop: 5,
marginLeft: 8,
},
btnDsb: {
justifyContent: 'center',
width: '95%',
borderRadius: 5,
backgroundColor: 'gray',
height: 40,
marginTop: 5,
marginLeft: 8,
},
txtBtn: {
textAlign: 'center',
color: '#00CED1',
fontSize: 20,
},
})
import React, { Component } from 'react';
import { StyleSheet, Text, View, Image, TouchableOpacity, FlatList, Linking, ActivityIndicator } from 'react-native';
import { Searchbar } from 'react-native-paper';
export default class Merchants extends Component {
constructor() {
super()
this.state = {
search: '',
loading: false,
merchantObj: [],
btnDisabled: true,
itemId: null,
imgLink: null,
listClicked: false,
itemindex:"",
}
this.arrayholder = [];
}
componentDidMount() {
const merchantUrl = 'http://165.227.43.115:8080/merchant/merchant'
fetch(merchantUrl)
.then(response => response.json())
.then(data => {
//var l_Data = [];
//for (var l_index = 0; l_index < data.length; l_index++)
//{
// l_Data[l_index] = {
// }
//}
this.setState({ merchantObj: data, loading: false },
function () {
this.arrayholder = data;
})
})
.catch(error => {
console.log(error)
});
}
search = text => {
console.log(text);
};
clear = () => {
this.search.clear();
};
SearchFilterFunction(text) {
//passing the inserted text in textinput
const newData = this.arrayholder.filter(function (item) {
//applying filter for the inserted text in search bar
const itemData = item.name ? item.name.toUpperCase() : ''.toUpperCase();
const textData = text.toUpperCase();
return itemData.indexOf(textData) > -1;
});
this.setState({
//setting the filtered newData on datasource
//After setting the data it will automatically re-render the view
merchantObj: newData,
search: text,
});
}
FlatListItemSeparator = () => {
return (
<View
style={{
height: 1,
width: "95%",
justifyContent: 'center',
backgroundColor: "#DCDCDC",
}}
/>
);
}
MerchSelected = (selectedId) => {
if (this.state.btnDisabled === true) {
return (
<View style={styles.btnDsb}>
<Text style={styles.txtBtn}>Select</Text>
</View>
)
} else {
return (
<TouchableOpacity onPress={(item) => this.props.navigation.navigate('Main', { itemId: this.state.itemId, itemImg: this.state.imgLink })}>
<View style={styles.btnSelect}>
<Text style={styles.txtBtn}>Select</Text>
</View>
</TouchableOpacity>
)
}
}
PressedItem = (itemId, itemImg) => {
console.log(itemId)
this.setState({ itemId: itemId, btnDisabled: false, imgLink: itemImg })
}
renderItem = ({ item }) => {
return (
<TouchableOpacity onPress={() => { this.PressedItem(item.id, item.image), this.setState({ itemindex: item.id }) }} >
<View style={this.state.itemindex == item.id ? styles.SelectedlistItem : styles.listItem} >
<Image
style={{ width: 80, height: 80 }}
source={{ uri: `${item.image}` }} />
<View style={{ flexDirection: 'column', marginLeft: 2 }}>
< Text style={{ fontWeight: 'bold', fontSize: 20 }} > {item.name} </Text>
{item.shoppingOption == 'STORE' ? <Text>Store</Text> : <Text>In-Store & Online</Text>}
<Text>${item.minAmount} - ${item.maxAmount}</Text>
<Text style={{ color: '#00CED1' }}
onPress={() => Linking.openURL(`${item.website}`)}>
view website
</Text>
</View>
</View>
</TouchableOpacity>
)
}
render() {
if (this.state.loading) {
return (
<View>
<Text>Loading...</Text>
</View>
);
}
return (
<View style={styles.container} >
<View style={styles.searchBar}>
<Searchbar
round
placeholder="Search"
onChangeText={text => this.SearchFilterFunction(text)}
onClear={text => this.SearchFilterFunction('')}
value={this.state.search}
/>
</View>
<View style={styles.merchantsList}>
<FlatList
data={this.state.merchantObj}
renderItem={this.renderItem}
ItemSeparatorComponent={this.FlatListItemSeparator}
keyExtractor={item => item.id.toString()}
extraData={this.state}
/>
</View>
<View style={styles.footerBtn}>
<TouchableOpacity onPress={() => this.props.navigation.navigate('Main', { itemId: undefined })}>
<View style={styles.btnSelect}>
<Text style={styles.txtBtn}>Cancel</Text>
</View>
</TouchableOpacity>
{this.state.btnDisabled === true ? this.MerchSelected('Sim') : this.MerchSelected('Nao')}
</View>
</View >
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#f2f2f4',
alignItems: 'center',
},
searchBar: {
flex: 1,
top: '5%',
width: '90%',
backgroundColor: 'rgba(242, 242, 244,0.5)'
},
merchantsList: {
flex: 6,
width: '95%',
},
footerBtn: {
flex: 1,
width: '100%',
},
listItem: {
flexDirection: 'row',
marginTop: 5,
},
SelectedlistItem: {
flexDirection: 'row',
marginTop: 5,
backgroundColor:"grey",
},
btnSelect: {
justifyContent: 'center',
width: '95%',
borderRadius: 5,
borderColor: '#00CED1',
borderStyle: 'solid',
borderWidth: 2,
height: 40,
marginTop: 5,
marginLeft: 8,
},
btnDsb: {
justifyContent: 'center',
width: '95%',
borderRadius: 5,
backgroundColor: 'gray',
height: 40,
marginTop: 5,
marginLeft: 8,
},
txtBtn: {
textAlign: 'center',
color: '#00CED1',
fontSize: 20,
},
})
above is code that's you want and the screenshot is here
Since you keep track of the selected itemId, you can simply override the style of selected item as below.
<TouchableOpacity onPress={() => this.PressedItem(item.id, item.image)} >
{/* Suppeose you want to change the background color of selected item as 'red' */}
<View style={item.id !== this.state.itemId ? styles.listItem : [styles.listItem, { backgroundColor: 'red' }]}>
...
</View>
</TouchableOpacity>
But you need to add extraData property in FlatList for telling the list to re-render.
extraData={this.state.itemId}
Hope this helps you. Feel free for doubts.

push button key value to array

I have a TouchableOpacity with key="red" and another one with key="black" and everytime a button is pushed the value should be added to the array. I've searched the internet for about one hour to find a way of doing this and I can't find nothing
import React from 'react';
import { StyleSheet, Text, TouchableOpacity, View } from 'react-native';
var RedBlackArray = [ "Red", "Black", "Red"];
export default class App extends React.Component {
constructor() {
super();
this.state = {
countRed: 0,
countBlack: 0
};
}
handlePressRed = () => {
this.setState(prevState => ({ countRed: prevState.countRed + 1 }));
}
handlePressBlack = () => {
this.setState(prevState => ({ countBlack: prevState.countBlack + 1 }));
}
render() {
return (
<View>
<View style={styles.container}>
<View style={styles.col1}>
<Text>red: {this.state.countRed}</Text>
<TouchableOpacity key='red' onPress={this.handlePressRed}>
<Text style={styles.button}>red!</Text>
</TouchableOpacity>
</View>
<View style={styles.col2}>
<Text>black: {this.state.countBlack}</Text>
<TouchableOpacity key='black' onPress={this.handlePressBlack}>
<Text style={styles.button.buton2}>black!</Text>
</TouchableOpacity>
</View>
</View>
<View style={{marginTop: 30, paddingLeft: 20, paddingRight:20}}>
<Text>So far the order has been:</Text>
{RedBlackArray.map((item, key)=> (
<Text key={key}>{item}</Text>
))}
</View>
</View>
);
}
}
const styles = StyleSheet.create({
button: {
marginTop: 50,
backgroundColor: 'red',
borderColor: 'white',
borderWidth: 1,
borderRadius: 12,
color: 'white',
fontSize: 20,
fontWeight: 'bold',
overflow: 'hidden',
padding: 12,
textAlign:'center',
buton2: {
marginTop: 50,
backgroundColor: 'black',
borderColor: 'white',
borderWidth: 1,
borderRadius: 12,
color: 'white',
fontSize: 20,
fontWeight: 'bold',
overflow: 'hidden',
padding: 12,
textAlign:'center'
}
},
container: {
flexDirection: 'row',
justifyContent: 'center'
},
col1: {
paddingTop: 50,
paddingRight: 30,
justifyContent: 'center'
},
col2: {
paddingTop: 50,
paddingLeft: 30,
justifyContent: 'center'
}
});
The result would be that when Red or Black buttons are pushed, the key should be added to the array so it would be displayed in the lower part of the code but I can't find the way of doing it. Here is the full code. Also, any other advice about what I'm doing wrong is welcomed. I've just started learning React. Thank you all!
You should read basic documentation of react-native the key prop in TouchableOpacity is work as id not for key value like array. Try following code
class Foo extends React.Component {
constructor(props) {
super(props);
this.state = {
red: 0,
black: 0,
};
}
onClick = (name) => {
let selected = this.state[name];
selected++;
this.setState({ [name]: selected });
};
renderButton = (name) => {
return (
<TouchableOpacity onPress={this.onClick.bind(this, name)}>
<Text>{name}</Text>
</TouchableOpacity>
);
};
render() {
const { red, black } = this.state;
return (
<View>
{this.renderButton('red')}
{this.renderButton('black')}
{red.map(() => (
<Text>red</Text>
))}
{black.map(() => (
<Text>black</Text>
))}
</View>
);
}
}
Maybe you could try to pass the desired value to the function called onPress to add it to the desired array.
handlePressBlack(passedValue) {
this.setState(prevState => ({ countBlack: prevState.countBlack + 1 });
this.setState(RedBlackArray: this.RedBlackArray.concat(passedValue));
}
<TouchableOpacity key='black' onPress={() => this.handlePressBlack('Black')}>
<Text style={styles.button.buton2}>black!</Text>
</TouchableOpacity>

Dismissible & extendable scroll view in React Native

I am looking for a way to achieve the effect shown below in React Native. I have already achieved the navigation bar and tab bar setup using React Navigation.
Now comes the part when I integrate a scroll like the one shown below. Effectively there is a view with lots of rows in it. I tried setting this up in a view wrapped in a ScrollView but this is too simplistic as the view just remains fixed on the screen and I'm looking to move the map with the view.
I'm looking for pseudocode if anything. Can anyone with experience in React Native suggest a good layout to achieve this effect?
I had a bit of fun with this. We could achieve the same effect by just creating a simple overlay on your map to display your list of services. The state of the overlay would be rendered visible from a callback on a button and rendered invisible by the 'refresh' or pull-down from an encapsulating <ScrollView />.
Here's what this component renders:
Here's the component class:
import React, { Component } from 'react';
import {
Text,
View,
TouchableOpacity,
StyleSheet,
ScrollView,
FlatList,
Dimensions,
RefreshControl
} from 'react-native';
export default class SO_MapOverlay extends Component {
constructor(props) {
super(props)
this.state = {
// Simple state variable to hide and show service list
serviceListVisible: false,
refreshing: false,
data: [
{ key: 'item1' },
{ key: 'item2' },
{ key: 'item3' },
{ key: 'item4' },
{ key: 'item5' },
{ key: 'item6' },
{ key: 'item7' },
{ key: 'item8' }
],
}
}
// Simply hides the button and shows the list overlay
showRouteOverview() {
this.setState({ serviceListVisible: true });
}
// Reverses showRouteOverview() when the user pulls down
onRefresh() {
this.setState({ refreshing: true });
this.setState({ serviceListVisible: false });
this.setState({ refreshing: false });
}
// Indicate the offset you want from the BOTTOM of the page
renderSpacer(offset) {
const { height } = Dimensions.get('window');
const spacerOffset = height - parseInt(offset);
return (
<View style={{ height: spacerOffset, backgroundColor: 'transparent' }} />
)
}
// Just renders each item in your flat list
renderItem(itemData) {
return(
<View style={[styles.item]}>
<Text style={{ color: 'black'}}>{itemData.item.key}</Text>
</View>
)
}
renderRefreshControl() {
return (
<RefreshControl
refreshing={this.state.refreshing}
onRefresh={this.onRefresh.bind(this)}
// Note: I'm just hiding the activity monitor with these paramteres
color='transparent'
tintColor='transparent'
/>
)
}
render() {
const { serviceListVisible } = this.state;
const listHeight = 56 * this.state.data.length;
return (
<View style={styles.container}>
<View style={styles.mapContainer}>
<Text style={{color: 'white'}}>I am map.</Text>
<TouchableOpacity
style={[styles.showRouteOverviewButton, serviceListVisible ? styles.invisible : null]}
onPress={() => { this.showRouteOverview() }}>
<Text>Show Services</Text>
</TouchableOpacity>
</View>
<ScrollView
style={[styles.listContainer, serviceListVisible ? styles.visible : null ]}
refreshControl={ this.renderRefreshControl() }>
{ this.renderSpacer(100) }
<FlatList
style={[styles.list, { height: listHeight }]}
data={this.state.data}
renderItem={(itemData) => this.renderItem(itemData)}
keyExtractor={(item, index) => index}
scrollEnabled={false}
/>
</ScrollView>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
visible: {
display: 'flex'
},
invisible: {
display: 'none'
},
mapContainer: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
backgroundColor: 'gray',
position: 'absolute',
bottom: 0,
right: 0,
left: 0,
top: 0,
},
showRouteOverviewButton: {
position: 'absolute',
bottom: 40,
backgroundColor: 'white',
paddingHorizontal: 20,
paddingVertical: 10,
},
listContainer: {
display: 'none',
flex: 1,
},
list: {
backgroundColor: 'red'
},
item: {
alignItems: 'center',
justifyContent: 'center',
flex: 1,
padding: 20,
backgroundColor: 'white',
}
});

Resources