React Native ScrollView/FlatList not scrolling - reactjs

I have a list of data that I want to place inside a FlatList (i.e. ScrollView) and whenever I try to scroll down to view more of the list, the ScrollView just bounces back up to the top of the list so I cannot ever see the bottom of the list.
I am using Expo and the strange thing is that if I just create a new project or copy/paste some code into Snack then the scrolling works just fine. It is only in my current project that I cannot get the list to scroll. I have even gone into the main.js file and just pasted my example code in there and the issue still persists.
My example code is located here: https://snack.expo.io/ryDPtO5-b
I have pasted this exact code into my project and it is not working as expected.
Versions:
RN 0.44 /
Expo SDK 17.0.0 /
React: 16.0.0-alpha.6
My actual use case for my project involves placing a FlatList component at the bottom-third of the screen to show a list of jobs. This is where I discovered the error and when I started to try and debug the issue. In my project I have a parent View with a style of {flex: 1 with a child of List that contains the FlatList... List comes from react-native-elements
EDIT
Here is the code I am actually trying to use:
<View style={styles.container}>
<View style={containerStyles.headerContainerStyle}>
<Text style={[textStyles.h2, { textAlign: "center" }]}>
Territory: {this.props.currentTerritory}
</Text>
</View>
<View style={styles.mapContainer}>
<MapView
provider="google"
onRegionChangeComplete={this.onRegionChangeComplete}
region={this.state.region}
style={{ flex: 1 }}
>
{this.renderMapMarkers()}
</MapView>
</View>
<Badge containerStyle={styles.badgeStyle}>
<Text>Orders Remaining: {this.props.jobsList.length}</Text>
</Badge>
<List containerStyle={{ flex: 1 }}>
<FlatList
data={this.props.jobsList}
keyExtractor={item => item.id}
renderItem={this.renderJobs}
removeClippedSubviews={false}
/>
</List>
</View>;
And all my styles
const styles = StyleSheet.create({
container: {
flex: 1,
},
mapContainer: {
height: 300,
},
badgeStyle: {
backgroundColor: 'green',
alignSelf: 'center',
marginTop: 15,
width: 300,
height: 35,
},
});
and lastly, my renderItem function:
renderJobs = ({ item }) => {
const { fullAddress, pickupTime } = item;
return (
<ListItem
containerStyle={
item.status === "active" && { backgroundColor: "#7fbf7f" }
}
title={fullAddress}
titleStyle={{ fontSize: 14 }}
subtitle={pickupTime}
subtitleStyle={{ fontSize: 12, color: "black" }}
onPress={() =>
this.props.navigation.navigate("jobActions", { job: item })
}
/>
);
};

I was able to solve this problem.
The Solution is to use flatlist component <View style={{flex:1}}> after renderRow return component <View style={{flex:1}}>

I am confused - are you using a FlatList or a ScrollView - these two elements have completely different lifecycle events (a FlatList requires you to define how individual rows will render and their keys, whereas a ScrollView expects the children to be rendered inline with the component).
Regardless, I think the issue is in your structure, it sounds like the List element requires a height attribute as well ({flex: 1}) as the absolute parent is filling it's space, but the List component is not having a pre-defined height.

So after debugging this a bit more today and I ended up just creating a brand new Expo project and copy/pasting all my source code into the new project, changing my Github project name to the new name, and setting the remote origin to my github repo (the one where I changed the name) on my new local project.
For some reason there appeared to be a problem with the Expo project. It almost seemed like the actual NAME of the project was causing issues as I attempted to change the NEW project's name to that of the original project, but it didn't work. It only worked when doing a brand new project, but using the exact same source code.

Related

Disable scroll in View on Expo Web

I am using Expo with react native and just have a plain view with some things inside of it. When I run the code as an expo app on my iPhone the view does not scroll vertically or horizontally (as I intended), but on web it does (Safari - after publishing with expo web). I want to disable this scrolling in both directions because I have swipeable components inside the view, so if the whole view moves things just don't work as intended.
How can I do that?
Here is my code:
<View style={styles.MainContainer}>
{this.state.Sample_Card_Array.map((item, key) => (
<SwipeableCard
key={key}
item={item}
removeCard={this.removeCard.bind(this, item.id)}
swipedRight={this.swipedRight.bind(this, item)}
swipedLeft={this.swipedLeft.bind(this, item.songID)}
/>
))}
{this.state.No_More_Card ? (
<View style={{flex:1}}>
<FlatList
data={this.state.songNamesApproved}
renderItem={this._renderItem}
/>
</View>
) : null}
</View>
this is maincontainer
MainContainer: {
flex: 1,
justifyContent:'center',
alignItems:'center',
paddingTop: Platform.OS === 'ios' ? 20 : 0,
},
I solved the problem by making the parent view a scrollview, now on Web it doesn't scroll left and right anymore when swiping.

does View in react native work same as Div in HTML?

I want to render multiple element with different styles but View and child View is not working as div working in html. please guide or share some resources to learn how multiple View work in a return.
import React from 'react'
import { Button, StyleSheet, Text, View,ScrollView } from 'react-native';
export default function Home() {
return (
<View>
<View style={styles.container}>
<Text >
Books are the best Mentors
</Text>
</View>
<View>
<Text style={{backgroundColor:'red'}} >
Books are the best Mentors
</Text>
</View>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
container2: {
flex: 1,
backgroundColor: '#fff',
position:'absolute',
bottom:0
},
});
TL;DR <div> is not the same as <View>
It looks like the possible issue here is that <div> have different default styling, and you should always remember about this. For example the default value for flexDirection in RN is column, not row like we have with <div>s.
Even though the official RN documentation about View tells us this:
View maps directly to the native view equivalent on whatever platform React Native is running on, whether that is a UIView, <div>, android.view, etc.
We should always keep in mind the specifics of the particular platform we work with.
I would also recommend you to check out this tutorial regarding Flex styling in React Native to get better experience working with it.

How to set SafeAreaView on Menu Drawer from React Native?

I am trying to implement SafeAreaProvider and SafeAreaView by the react-native-safe-area-context lib in my react-native application. My version of react-navigation is 5.
I followed the documentation provided in react-navigation, I wrapped my NavigationContainer within SafeAreaProvider. And inside my views I inserted the SafeAreaView. But my drawer still fills the space on my entire screen.
How I need my drawer keep:
Can you tell me how I can make SafeAreaView work in my Menu Drawer?
I put my project in the snack.expo.io so that they can see the whole project.
Thank you very much in advance!!
Style the content of your drawer in a nested View component ... not in the SafeAreaView
<SafeAreaView style={{ backgroundColor: '#fff', flex: 1 }}>
{/* Here's your container */}
<View style={{ flex: 1, backgroundColor: '#3b8324', borderTopLeftRadius: 25, }}>
<Text>Start</Text>
</View>
</SafeAreaView>
To change the width of your drawer, set drawerWidth of your DrawerNavigatorConfig
const DrawerNavigatorConfig = {
/** ...configs */
drawerWidth: 200,
};
React Nav 5
<Drawer.Navigator
drawerStyle={{ width: YOUR_DRAWER_WIDTH }}
/**...*/
>

React Native Vector Icons to fit the parent container without specifying a size prop (Expo)

I am using the Vector Icons provided by Expo https://github.com/expo/vector-icons
All the icons accept a size prop which is an integer, my use case is to see if there to adjust the icon size automatically based on the parent container without having to worry about the size on each and every device (because size seems to translate the same on every device despite the pixel ratio, dimensions etc)
<View style={{flex: 1}}>
<Icon name="app-logo" size={30} color="white" />
</View>
I tried setting style prop to the below options but no luck. I would expect that this will not work because after all they are just fonts and they need not support the width and height
<Icon name="app-logo" style={{flex: 1}} color="white" />
<Icon name="app-logo" style={{width: '100%', height: '100%' color="white" />
Is there any cleaner or suggested way to do this? Using dimensions and ratios of the devices is my last resort.
Here's an example of the closest I've been able to get:
import React from "react"
import { PixelRatio, StyleSheet, Text, View } from "react-native"
import { FontAwesome5 } from "#expo/vector-icons"
const getRandomNumber = (min: number, max: number) =>
Math.random() * (max - min) + min
const App = () => {
const [containerSize, setContainerSize] = React.useState<number>()
const randomDimension = React.useMemo(() => getRandomNumber(100, 400), [])
return (
<View
style={{
flex: 1,
backgroundColor: "green",
justifyContent: "center",
alignItems: "center",
}}
>
<View
style={{
width: randomDimension,
aspectRatio: 1,
backgroundColor: "red",
}}
onLayout={layoutEvent =>
setContainerSize(layoutEvent.nativeEvent.layout.width)
}
>
<FontAwesome5
name="app-store-ios"
size={containerSize || 0}
style={{
textAlign: "center",
}}
color="blue"
/>
</View>
</View>
)
}
export default App
There's some code in here for intentionally setting the icon's parent container to a randomly sized square, and setting that on the parent, which is not what you're looking for but helped me re-create the problem.
The interesting bit which I think you want is the value passed to the onLayout prop of the icon's parent container, which gets the parent container's size and puts it in state. That state will be empty until the first render & layout, so we have the size of the vector icon defaulted to 0 until that happens. Once we have the size of the parent in state, we can set the size of the vector icon.
The vector icon doesn't actually fill the entire parent's space, but it scales pretty close pretty well. I think that's because the vector icon itself is a font under the hood (?) and we're trying to set the size of a font (in pts really (I think?)) to the size we got from the parent in pixels. There's probably a neat way to clean that up using react native's PixelRatio - I spent a bit of time trying but couldn't get it working perfectly.
The last thing I needed to do (since the vector icon doesn't actually "fill" the parent view was set the style prop of the icon to { textAlign: 'center' } which keeps it centered horizontally within the parent view (it was already centered vertically) instead of being off on the left side of the parent.
You could jank the whole thing into submission by adding a constant to the size and putting a negative top margin on the icon, but that's kind of a PITA and obviously not a great solution (it'll be a headache every time you do it and it probably won't work cross platform / on different pixel densities).
The reason I actually arrived at this post was because I was trying to keep an icon the same size as text; I was working on an app which showed a comment count (integer) to the left of a little chat bubble icon and I wanted the icon's size to scale with the system's font size if the user had adjusted that in their system settings. This is actually easier (which makes sense given my theory about vector icons being represented as fonts under the hood is correct). Here's the relevant part of my code that achieved that:
const StoryCommentsLabel = ({ story }: { story: HackerNewsItem }) => {
const navigation = useNavigation()
const numberOfComments = story.descendants || 0
const doNotActTouchable = { activeOpacity: 1 }
const fontScale = React.useMemo(() => PixelRatio.getFontScale(), [])
const defaultFontSize = 14
const iconSize = defaultFontSize * fontScale
return (
<TouchableOpacity
style={{ flexDirection: "row", justifyContent: "flex-end" }}
onPress={() => {
if (numberOfComments > 0) {
navigation.navigate("Story Comments", { story })
}
}}
hitSlop={{ top: 20, left: 20, bottom: 20, right: 20 }}
{...(numberOfComments === 0 ? doNotActTouchable : {})}
>
<Text
style={{
fontSize: 14,
color: PlatformColor("secondaryLabel"),
}}
>
{numberOfComments}
</Text>
<View style={{ width: 5 }} />
<Ionicons
name="chatbubble"
size={iconSize}
color={PlatformColor("secondaryLabel")}
/>
</TouchableOpacity>
)
}
There's more in there than you need, but the important bit is this:
const fontScale = React.useMemo(() => PixelRatio.getFontScale(), [])
const defaultFontSize = 14
const iconSize = defaultFontSize * fontScale

React Native, State Changes, but Component Does not Properly Render

I am working on my first React Native app, but feel very comfortable with React and React Native, which had me surprised regarding this snag.
Goal - Allow users to dynamically create tabs based on their input needs. Each tab reuses the same component which is a form. On press, the tab should display its corresponding form. The form works fine.
The Problem - The parent component won't render the correct form on tab press. The state updates, but the component remains on the first component selected. If I list the forms on one screen, one after the other, I can see that filling out one form, has no effect on the others (desired effect).
If I don't render a form component, it will work on the first tab press. This makes me wonder if React is not registering a change in the parent component because the child is always a copy of the other child components I desire displayed.
I approached this problem three ways with no luck.
Creating an array of components stored in the parent components state. Creating a new tab pushes a form component into the current array in state by grabbing that array, pushing a component into it and reseting state with that new array. I store a key in state that updates on a screen press. That key matches with an index in the component array to show the selected component with its corresponding tab.
Using componentDidUpdate to set a displayed component on the screen press similar to above, but instead of keying into the array in my render function, in the componentDidUpdate function manually setting a displayedView on each update of the component and using that in render.
Currently (code to follow) - Creating unique keys in state for each tab added that stores the corresponding component and using that to decipher which component to display.
I decided to put a ref on some child components. I used console.log before render to see if that correct component was being selected for display. It showed it was, but it was not appearing that way on the screen. I also added the tab number being clicked to the UI and I can see my state updating properly on each tab press.
Any help would be appreciated. Last resort I am going to have a user select the amount of forms they want before reaching this view and create it based on that number, but I would prefer not to.
import React from 'react';
import { connect } from 'react-redux';
import { View, Text, TouchableOpacity } from 'react-native';
import CreateWorkoutSet from './CreateWorkoutSet';
import { Entypo } from '#expo/vector-icons';
class CreateWorkout extends React.Component {
constructor(props) {
super(props)
this.state = {
workoutSetTemplate0: <CreateWorkoutSet navigation={this.props.navigation}/>,
showTab: 0,
totalTabs: 0,
}
this.addWorkoutSetToGroup = this.addWorkoutSetToGroup.bind(this)
}
componentDidMount() {
this.setState({
tabs: [
<TouchableOpacity onPress={() => this.setState({ showTab: 0 })}>
{ this.state.showTab === 0 ? (
<View style={{ backgroundColor: 'red', height: 40, width: 40 }}>
<Text>Tab: 1</Text>
</View>
) :
<View style={{ backgroundColor: 'grey', height: 40, width: 40 }}>
<Text>Tab: 1</Text>
</View>
}
</TouchableOpacity>
],
showTab: 0
})
}
addWorkoutSetToGroup() {
let tabNumber = this.state.totalTabs + 1
let tabKey = `workoutSetTemplate${tabNumber}`
let tabs = this.state.tabs
tabs.push(
<TouchableOpacity onPress={() => this.setState({ showTab: tabNumber })}>
{ this.state.showTab === tabNumber ? (
<View style={{ backgroundColor: 'red', height: 40, width: 40 }}>
<Text>Tab: {tabNumber}</Text>
</View>
) :
<View style={{ backgroundColor: 'grey', height: 40, width: 40 }}>
<Text>Tab: {tabNumber}</Text>
</View>
}
</TouchableOpacity>
)
this.setState({ [tabKey]: <CreateWorkoutSet navigation={this.props.navigation} />, tabs: tabs, totalTabs: tabNumber })
}
render() {
let template = this.state[`workoutSetTemplate${this.state.showTab}`]
let tabs = this.state.tabs
return(
<View style={{flex: 1, justifyContent: 'center', marginTop: 20}}>
<View style={{ flexDirection: 'row' }}>
{tabs}
</View>
<View>
<Text>Add Exercise To Group</Text>
<TouchableOpacity onPress={()=> this.addWorkoutSetToGroup()}>
<Entypo name='circle-with-plus' size={36} color='red' />
</TouchableOpacity>
</View>
<TouchableOpacity onPress={() => this.props.navigation.navigate('BlockDetail')}>
<Text>Blocks</Text>
</TouchableOpacity>
<Text>{this.state.showTab}</Text>
{template}
</View>
)
}
}
export default connect()(CreateWorkout)
Try calling
this.forceUpdate()
after updating the state

Resources