Dismissible & extendable scroll view in React Native - reactjs

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',
}
});

Related

How to disable the Touchable Opacity for some specific item that comes from API in React Native

I have an API and I need to set the Touchable Opacity separately
for each item. I have done it already. but now I need to disable some
Touchable Opacity. Like what I am trying to do is if Status and Check in Api both are True then the User can move to next and the color of it is red.
screen by pressing on that touchable opacity but if one of them is
false then the touchable opacity will be disabled and a user can't move to the next screen and the color of it will be grey I don't know how to do it because I am very new in this kind of functionality in React-native I read different questions and answers but unfortunately that doesn't help me.
API Response
const results = [
{
Id: "IySO9wUrt8",
Name: "Los Stand",
Category: "Mexican",
Status: true,
Check: true,
},
{
Id: "IySO9wUrt8",
Name: "Los Stand 2",
Category: "Burger",
Status: true,
Check: true,
},
{
Id: "IySO9wUrt8",
Name: "Los Stand 3",
Category: "BBq",
Status: true,
Check: true,
},
];
My full code
App.js
import React, { useEffect, useState } from 'react';
import {
View,
Text,
TouchableOpacity,
StyleSheet,
FlatList,
SafeAreaView,
} from 'react-native';
import { Card, TextInput, RadioButton } from 'react-native-paper';
const results = [
{
Id: 'IySO9wUrt8',
Name: 'Los Stand',
Category: 'Mexican',
Status: true,
Check: true,
},
{
Id: 'IySO9wUrt8',
Name: 'Los Stand 2',
Category: 'Burger',
Status: true,
Check: true,
},
{
Id: 'IySO9wUrt8',
Name: 'Los Stand 3',
Category: 'BBq',
Status: true,
Check: true,
},
];
export default function App() {
const renderItem = ({ item }) => {
return (
<>
<TouchableOpacity
onPress={() =>
navigation.navigate('NextScreen', {
name: item.Name,
phone: item.Phone,
})
}>
<View
style={{
flexDirection: 'column',
marginHorizontal: 10,
marginVertical: 10,
padding: 5,
backgroundColor: 'grey',
borderRadius: 15,
}}>
<View
style={{
flexDirection: 'column',
alignSelf: 'flex-start',
marginTop: 10,
marginBottom: 10,
}}>
<Text
style={{
fontSize: 15,
fontWeight: '900',
color: '#424242',
}}>
{item.Name}
</Text>
<Text>{item.Phone}</Text>
</View>
</View>
</TouchableOpacity>
</>
);
};
return (
<SafeAreaView style={styles.container}>
<FlatList
style={styles.container}
data={results}
renderItem={renderItem}
keyExtractor={(item, index) => index.toString()}
/>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
container: {
flex: 2,
backgroundColor: 'white',
},
});
AssetExample.js
import * as React from 'react';
import { Text, View, StyleSheet, Image } from 'react-native';
export default function AssetExample() {
return (
<View style={styles.container}>
<Text style={styles.paragraph}>
Local files and assets can be imported by dragging and dropping them into the editor
</Text>
<Image style={styles.logo} source={require('../assets/snack-icon.png')} />
</View>
);
}
const styles = StyleSheet.create({
container: {
alignItems: 'center',
justifyContent: 'center',
padding: 24,
},
paragraph: {
margin: 24,
marginTop: 0,
fontSize: 14,
fontWeight: 'bold',
textAlign: 'center',
},
logo: {
height: 128,
width: 128,
}
});
You can edit code in Expo Snack
My Expo code for live editing
TouchableOpacity has a disabled property, so you should use it. It will prevent onPress from executing and navigating the user.
As for the styling, you can check the style using the same login.
<TouchableOpacity disabled={!result.Status || !result.Check} style={{ background: (!result.Status || !result.Check) ? 'grey' : 'red' }}>

React Native ListItem will not show any titles or chevrons

Screen shot of current list
I would like to display a list that is grabbing data from my Firebase Firestore Database. I'm using expo for this project and react native. I can see the boxes form the list, but can't see the titles or the chevrons. I've tried a few formatting solutions and couldn't resolve the issue. I'm still able to add to the list and view the list once clicked.
import React, { Component } from "react";
import {
StyleSheet,
ScrollView,
ActivityIndicator,
View,
Text,
} from "react-native";
import { colors, ListItem } from "react-native-elements";
import firebase from "../../firebase";
class SearchEntry extends Component {
constructor() {
super();
this.firestoreRef=
firebase.firestore().collection("RepairDocuments");
this.state = {
isLoading: true,
entryArr: [],
};
}
componentDidMount() {
this.unsubscribe =
this.firestoreRef.onSnapshot(this.getCollection);
}
componentWillUnmount() {
this.unsubscribe();
}
getCollection = (querySnapshot) => {
const entryArr = [];
querySnapshot.forEach((res) => {
const { unit, date, bio } = res.data();
entryArr.push({
key: res.id,
res,
unit,
date,
bio,
});
});
this.setState({
entryArr,
isLoading: false,
});
};
render() {
if (this.state.isLoading) {
return (
<View style={styles.preloader}>
<ActivityIndicator size="large" color="#9E9E9E" />
</View>
);
}
return (
<ScrollView style={styles.container}>
{this.state.entryArr.map((item, i) => {
return (
<ListItem
titleStyle={{ color: "black", fontWeight: "bold" }}
style={{ height: 55 }}
key={i}
chevron
title={item.unit}
subtitle={item.bio}
onPress={() => {
this.props.navigation.navigate("Details", {
userkey: item.key,
});
}}
/>
);
})}
</ScrollView>
);
}
}
const styles = StyleSheet.create({
container: {
paddingTop: 55,
flex: 1,
fontSize: 18,
textTransform: "uppercase",
fontWeight: "bold",
backgroundColor: colors.black,
},
preloader: {
left: 0,
right: 0,
top: 0,
bottom: 0,
position: "absolute",
alignItems: "center",
justifyContent: "center",
},
text: {
color: colors.black,
backgroundColor: colors.black,
},
});
export default SearchEntry;

ScrollView not enabling scrolling in React Native App

Using Expo and the Android App, the <ScrollView> section is not scrolling. I've read through a lot of related questions but can't seem to nail down the issue. Can anyone shed some light for a react native newbie?
On pressing the button the API is requested and then the article headlines are rendered. This all works great but the headlines are not scrollable and don't all fit on the screen. Am I misunderstanding what <ScrollView> does?
import React from 'react'
import { ScrollView, StyleSheet, Text, View, Footer } from 'react-native'
import { Font } from 'expo'
import { Button } from 'react-native-elements'
export default class App extends React.Component {
state = {
fontLoaded: true,
buttonPressed: false,
showButton: true
}
onPress = (e) => {
e.preventDefault()
this.fetchArticlesAsync()
this.setState({
buttonPressed: true,
showButton: false
})
}
async fetchArticlesAsync() {
await fetch('http://content.guardianapis.com/search?show-fields=all&api-key=d8d8c012-6484-4bb4-82d7-2770a7c5d029')
.then(response => response.json())
.then(responseJSON => {
return this.setState({
articleList: responseJSON.response.results
})
})
.catch((error) => {
console.log(error)
})
}
render() {
return (
<ScrollView contentContainerStyle={styles.container}>
{
this.state.fontLoaded &&
<View style={this.state.buttonPressed ? styles.newsContainer : styles.container}>
<Text style={styles.header}>Newsli</Text>
{this.state.showButton &&
<Button buttonStyle={styles.button} onPress={this.onPress} clear text='Feed Me' />}
{this.state.buttonPressed &&
<View>
{this.state.articleList ?
<View style={styles.articleContainer}>
{this.state.articleList.map((article, i) => {
return <View style={styles.articleContainer} key={i}>
<Text style={styles.text}>{article.webTitle}</Text>
</View>
})}
</View> : <Text style={styles.loading}>Loading</Text>}
</View>}
</View>
}
</ScrollView>
)
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#000',
alignItems: 'center',
justifyContent: 'center',
},
newsContainer: {
flex: 1,
backgroundColor: '#000',
alignItems: 'center',
justifyContent: 'flex-start',
marginTop: 15
},
articleContainer: {
flex: 1,
backgroundColor: '#000',
alignItems: 'flex-start',
justifyContent: 'flex-start',
marginTop: 15
},
header: {
fontSize: 90,
color: '#fff'
},
text: {
fontSize: 20,
color: '#fff',
},
loading: {
fontSize: 24,
color: '#FF69B4'
},
button: {
borderColor: '#fff',
borderWidth: 2
}
})
It was quite small mistake. The ScrollView does not know current device's screen size. You need to have ScrollView inside a View component. Try the following...
<View style={styles.container}>
<ScrollView>
{...}
</ScrollView>
</View>
Next time when you submit a question strip out all nonsense code. Like Font fetching.
Good luck!

react native: rendering cards that flip on user tap

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>
)
});
}

Avoid view to be refreshed in a tab based app in react Native

I have a simple tabbar app.
One of the tabs renders a view where I display pictures fetched remotely.
The issue I'm facing is, whenever I switch tabs, the images are re-displayed, despite the fact than neider render is called again, nor shouldComponentUpdate.
This results in a very bad user experience as the view is refreshed over and over again.
Any idea of how I can fix it?
/**
* Sample React Native App
* https://github.com/facebook/react-native
* #flow
*/
import React, { Component } from 'react';
import {
AppRegistry,
StyleSheet,
Text,
View,
TabBarIOS,
ScrollView,
Image,
Dimensions
} from 'react-native';
var {height, width} = Dimensions.get('window');
class TabIssue extends Component {
constructor(props){
super(props);
this.state = {
selectedTab: 'tab1',
items: []
}
}
componentDidMount() {
// Build an array of 60 photos
let items = Array.apply(null, Array(60)).map((v, i) => {
return { id: i, src: 'http://placehold.it/200x200?text='+(i+1) }
});
this.setState({ items });
}
render() {
return (
<TabBarIOS>
<TabBarIOS.Item title="Tab #1"
selected={this.state.selectedTab === 'tab1'}
onPress={() => {
this.setState({ selectedTab: 'tab1', });
}}>
<View style={{marginTop: 20}}>
<Text> toto</Text>
</View>
</TabBarIOS.Item>
<TabBarIOS.Item title="Tab #2"
selected={this.state.selectedTab === 'tab2'}
onPress={() => {
this.setState({ selectedTab: 'tab2', });
}}>
<ScrollView>
<View style={styles.galleryContainer}>
{
this.state.items.map((item) => {
return(
<View key={item.id} style={styles.pictureContainer}>
<Image source={{uri: item.src}} style={styles.picture} />
</View>
)
})
}
</View>
</ScrollView>
</TabBarIOS.Item>
</TabBarIOS>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF',
},
welcome: {
fontSize: 20,
textAlign: 'center',
margin: 10,
},
instructions: {
textAlign: 'center',
color: '#333333',
marginBottom: 5,
},
galleryContainer: {
flexWrap: 'wrap',
flexDirection: 'row',
flex: 1,
backgroundColor: 'red'
},
picture: {
height: width/2 - 4,
width: width/2 -4,
flex: 1,
},
pictureContainer: {
padding: 2
}
});
AppRegistry.registerComponent('TabIssue', () => TabIssue);

Resources