FlatList inside ScrollView doesn't scroll - react-native

I've 4 FlatLists with maxHeight set to 200 inside a ScrollView.
<ScrollView>
<FlatList/>
<FlatList/>
<FlatList/>
<FlatList/>
</ScrollView>
and when I try to scroll a FlatList, it doesn't scroll but the ScrollView scrolls. How do I fix this issue ?
Full Source Code
import { Component, default as React } from 'react';
import { FlatList, ScrollView, Text } from 'react-native';
export class LabScreen extends Component<{}> {
render() {
return (
<ScrollView>
{this.renderFlatList('red')}
{this.renderFlatList('green')}
{this.renderFlatList('purple')}
{this.renderFlatList('pink')}
</ScrollView>
);
}
getRandomData = () => {
return new Array(100).fill('').map((item, index) => {
return { title: 'Title ' + (index + 1) };
});
};
renderFlatList(color: string) {
return (
<FlatList
data={this.getRandomData()}
backgroundColor={color}
maxHeight={200}
marginBottom={50}
keyExtractor={(item, index) => index.toString()}
renderItem={({ item }) => <Text>{item.title}</Text>}
/>
);
}
}
snack.expo link

We can use the built-in nestedscrollenabled prop for the children FlatList/ScrollView components.
<FlatList nestedScrollEnabled />
This is only required for Android (Nested scrolling is supported by default on iOS).

I was having a very similar issue until I came across an almost complete solution in a very helpful comment on one of the GitHub issues for the react-native project: https://github.com/facebook/react-native/issues/1966#issuecomment-285130701.
The issue is that the parent component is the only one registering the scroll event. The solution is to contextually decide which component should actually be handling that event based on the location of the press.
You'll need to slightly modify your structure to:
<View>
<ScrollView>
<View>
<FlatList />
</View>
<View>
<FlatList />
</View>
<View>
<FlatList />
</View>
<View>
<FlatList />
</View>
</ScrollView>
</View>;
The only thing I had to change from the GitHub comment was to use this._myScroll.contentOffset instead of this.refs.myList.scrollProperties.offset.
I've modified your fully working example in a way that allows scrolling of the inner FlatLists.
import { Component, default as React } from "react";
import { View, FlatList, ScrollView, Text } from "react-native";
export default class LabScreen extends Component<{}> {
constructor(props) {
super(props);
this.state = { enableScrollViewScroll: true };
}
render() {
return (
<View
onStartShouldSetResponderCapture={() => {
this.setState({ enableScrollViewScroll: true });
}}
>
<ScrollView
scrollEnabled={this.state.enableScrollViewScroll}
ref={(myScroll) => (this._myScroll = myScroll)}
>
{this.renderFlatList("red")}
{this.renderFlatList("green")}
{this.renderFlatList("purple")}
{this.renderFlatList("pink")}
</ScrollView>
</View>
);
}
getRandomData = () => {
return new Array(100).fill("").map((item, index) => {
return { title: "Title " + (index + 1) };
});
};
renderFlatList(color: string) {
return (
<View
onStartShouldSetResponderCapture={() => {
this.setState({ enableScrollViewScroll: false });
if (
this._myScroll.contentOffset === 0 &&
this.state.enableScrollViewScroll === false
) {
this.setState({ enableScrollViewScroll: true });
}
}}
>
<FlatList
data={this.getRandomData()}
backgroundColor={color}
maxHeight={200}
marginBottom={50}
keyExtractor={(item, index) => index.toString()}
renderItem={({ item }) => <Text>{item.title}</Text>}
/>
</View>
);
}
}
Hopefully you find this useful!

This is the simplest answer that requires zero configuration.. and it works like a charm
<ScrollView horizontal={false}>
<ScrollView horizontal={true}>
<Flatlist
....
....
/>
</ScrollView>
</ScrollView>

I fixed my problem with nested FlatList not being able to scroll items on android by simply importing FlatList
import { FlatList } from 'react-native-gesture-handler';
If this would not work, also try to import ScrollView.
import { ScrollView } from 'react-native';
// OR
import { ScrollView } from 'react-native-gesture-handler';
You need to play around with these imports, at least it worked in my case.

Try to set the FlatList as nested
nestedScrollEnabled={true}

Using View with a flex:1 instead of ScrollView worked for me.

Use map instead of Flatlist, same result and don't break the application
Minha conta
{
buttonsProfile.map(button => (
<ArrowButton
key={button.key}
title={button.title}
iconName={button.icon}
toogle={button.toogle}
onPress={() => {navigation.navigate(button.route)}}
/>
))
}

The better answer is to put a horizontal ScrollView inside of the other ScrollView and then the FlatList

Related

Use .map not flatlist with activityIndicator in react native

I am getting a list of data using map not flatlist, I want to use .map not flatlist, when I apply ActivityIndicator to it while fetching the data, it did not work see below
Below is my code
<View>
{dataList.map((dataList, index) =>
<Text key={index} >{dataList.category_name}</Text>
<Text key={index} >{dataList.category_number}</Text>
)}
</View>
When I tried it with ActivityIndicator see below
{ dataList ?
<View>
{dataList.map((dataList, index) =>
<Text key={index} >{dataList.category_name}</Text>
<Text key={index} >{dataList.category_number}</Text>
)}
</View>
:
<ActivityIndicator />
}
It the not work, I will need your help with this.
Thanks in advance
Try using a boolean for your conditional render such as a loading state which you can easily toggle on and off at the beginning of the fetch and at the end respectively. You can also state the logic of your map outside of your component so it looks way cleaner and easy to read like this example:
import React from 'react';
import { Text, View, ActivityIndicator } from 'react-native';
const dataList = [
{ category_name: 'pop', category_number: 1 },
{ category_name: 'dance', category_number: 2 },
{ category_name: 'hiphop', category_number: 3 },
];
const renderDataList = () => {
return dataList.map((dataList, index) => (
<View>
<Text>{dataList.category_name}</Text>
<Text>{dataList.category_number}</Text>
</View>
));
};
const App = () => {
const [isLoading, setIsloading] = React.useState(false);
return <View>{isLoading ? <ActivityIndicator /> : renderDataList()}</View>;
};
export default App;
Your output would be for false:
Your output for true would be:

How to implement Activity indicator to render until flat list data is displayed? React Native

I have a flat list with some hardcoded data. How can I implement the Activity indicator to spin and to be displayed until the flat list data is completely displayed on the screen? Below is my code. Thanks
import React, {useState} from 'react';
import { View, FlatList, TouchableOpacity, ActivityIndicator } from 'react-native';
import { MainScreenCard } from '../mainScreen.components/mainScreen.card';
import { Spacer } from '../assets.driveAround/spacer';
export const MainScreen = ({navigation}) => {
return(
<>
<FlatList
data={[
{ name: 1 },
{ name: 2 },
{ name: 3 },
{ name: 4 }
]}
renderItem={() => (
<TouchableOpacity onPress={() => navigation.navigate("Login")}>
<Spacer position="bottom" size="large">
<MainScreenCard/>
</Spacer>
</TouchableOpacity>
)}
keyExtractor={(item) => item.name}
contentContainerStyle={{ padding: 16 }}
/>
</>
);
}
There are two main options depending on how you want to refresh. If you want to pull from the top of the screen to refresh provide it a refresh control
https://reactnative.dev/docs/refreshcontrol
<ScrollView
contentContainerStyle={styles.scrollView}
refreshControl={
<RefreshControl
refreshing={refreshing}
onRefresh={onRefresh}
/>
}
>
<Text>Pull down to see RefreshControl indicator</Text>
</ScrollView>
Otherwise, another simple option is to use an ActivityIndicator to show a spinner overtop of the view
https://reactnative.dev/docs/activityindicator
const [loading, setLoading] = useState(false)
...
<ActivityIndicator size="large" color="#00ff00" animating={loading}/>

React Native TouchableOpacity onPress not working with FlatList

I created a FlatList of selectable countries like this
export default function Body() {
const data = useData();
const [selected, setSelected] = useState(1);
const renderItem = ({ item }) => {
return item.empty ? (
<View style={styles.blank} />
) : (
<Country
text={item.name}
onPress={() => setSelected(item.id)}
selected={item.id === selected}
>
<item.Flag />
</Country>
);
};
return (
<View style={styles.container}>
<FlatList
data={data}
renderItem={renderItem}
keyExtractor={(item) => item.id.toString()}
extraData={selected}
showsVerticalScrollIndicator={false}
contentContainerStyle={styles.flatlist}
numColumns={3}
/>
</View>
);
}
And I defined a Country component like this
import React from "react";
import { View, Text, TouchableOpacity } from "react-native";
import { styles } from "./Style";
export default function Country({ children, text, onPress, selected }) {
return (
<View style={styles.container}>
<View style={[styles.view, selected && styles.selected]}>
<TouchableOpacity onPress={onPress}>{children}</TouchableOpacity>
</View>
<TouchableOpacity onPress={onPress}>
<Text style={styles.text}>{text}</Text>
</TouchableOpacity>
</View>
);
}
Everything was working perfectly fine and I was able to select a country but somehow now the touchableOpacity onPress event seems broken and the elements are no longer pressable. for things to work I need to edit and undo edit on the data array or the Country component. Is this a RN bug causing things to fail?
I found the root problem. Turns out that the problem lays on the stylesheet of the Country component wrapper. I gave it an alignSelf: "baseline" don't know why but removing it solved the hassle

React Native Flatlist numColumns is not making multiple columns

I have just started to learn React Native, and this is my first project - a news app. However, I have successfully rendered the image and description of the news using React Native Flatlist.
But when I am using numColumns for making two columns, the column number remains the same. But the number of the shown images becomes the half (i.e. from 18 to 9 in my case). And also the description of the image is going under the image of the next news like shown below -
      
My source code looks like below -
import React, { Component } from 'react'
import { View, Text, FlatList, TouchableHighlight, SectionList, TouchableOpacity , Image,StyleSheet } from 'react-native'
import { ScrollView } from 'react-native-gesture-handler';
import { HomeNewsJSON } from "../../../../assects/JSON/Home"
class HomeNews extends Component {
constructor(props) {
super(props);
this.state = {
news: ""
};
}
componentDidMount() {
this.getInfo();
}
getInfo() {
var data = []
var jsondata = HomeNewsJSON["items"]
// alert(jsondata.length)
this.setState({
news: jsondata
})
}
renderImage(item) {
var a = item;
return (
<TouchableOpacity
style={{flex:1/3,
aspectRatio:1}}>
<Image style={{flex: 1}} resizeMode='cover' source={{ uri: (a.slice(a.indexOf("src") + 5, a.indexOf("</a>") - 3))}}></Image>
</TouchableOpacity>
)
}
renderPic(data) {
var a = data;
return (a.slice(a.indexOf("src") + 5, a.indexOf("</a>") - 3));
}
render() {
var result = Object.entries(this.state.news);
console.log(result)
return (
<View>
<FlatList
contentContainerStyle={{margin:2}}
style={{borderWidth: 0}}
horizontal={false}
numColumns={2}
keyExtractor={item => item.findIndex}
data={result}
renderItem={({ item }) => (
<View>
{this.renderImage(item[1]["description"])}
<Text>{item[1]["description"]}</Text>
</View>
)}
/>
</View>
)
}
}
export default HomeNews
Please, anyone, suggest me a way to fix it. I really appreciate any help you can provide.
try flex:1 with View outside of item
renderItem={({ item }) => (
<View
style={{
flex: 1,
flexDirection: 'column',
}}>
//image here
</View>
)
Let's try add flexDirection : 'row' in view, such as:
<View style={{flexDirection : 'row'}}>
{this.renderImage(item[1]["description"])}
<Text>{item[1]["description"]}</Text>
</View>

react-native TouchableNativeFeedback onPress not working

I have created a composed component to compose TouchableNativeFeedback to wrapperComponent.
export default function withFeedback2(
WrappedComponent
) {
return class extends BaseComponent {
constructor(props) {
super(props);
}
render() {
return (
<View>
<TouchableNativeFeedback
onPress={() => this.props.onContainerViewPress()}
>
<WrappedComponent {...this.props} />
</TouchableNativeFeedback>
{/* <TouchableOpacity
onPress={this.props.onContainerViewPress ? () => this.props.onContainerViewPress() : null}
>
<WrappedComponent {...this.props} />
</TouchableOpacity> */}
</View>
);
}
};
}
But OnPress event of TochableNativeFeedback is not firing. Whereas OnPress event is fired correctly and onContainerViewPress prop of wrappercomponent is called if wrappercomponent wrapped under TouchableOpacity.
I am testing this on the Android Platform.
Use a <View></View> to wrap your WrappedComponent for TouchableNativeFeedback.
<TouchableNativeFeedback
onPress={() => this.props.onContainerViewPress()}>
<View>
<WrappedComponent {...this.props} />
</View>
</TouchableNativeFeedback>
There are two different TouchableNativeFeedback classes. Make sure you import the correct one:
import { TouchableNativeFeedback } from "react-native"
import { TouchableNativeFeedback } from "react-native-gesture-handler"
I had a similar problem and finally used it from "react-native" library. Importing it from "react-native-gesture-handler" did not work for me.
I've discovered that adding a Ripple effect to the TouchableNativeFeedback fixes the issue for me:
background={TouchableNativeFeedback.Ripple("#FFFFFF",true)}
i.e.
export default function withFeedback2(
WrappedComponent
) {
return class extends BaseComponent {
constructor(props) {
super(props);
}
render() {
return (
<View>
<TouchableNativeFeedback
onPress={() => this.props.onContainerViewPress()}
background={TouchableNativeFeedback.Ripple("#FFFFFF",true)}
>
<WrappedComponent {...this.props} />
</TouchableNativeFeedback>
</View>
);
}
};
}
Try: useForeground={true}
<TouchableNativeFeedback onPress={() => {}} useForeground={true}>
You can call method as below:
export default function withFeedback2(
WrappedComponent
) {
return class extends BaseComponent {
constructor(props) {
super(props);
this.onContainerViewPress = this.onContainerViewPress.bind(this);
}
onContainerViewPress() {
const { onContainerViewPress } = this.props;
onContainerViewPress();
}
render() {
return (
<View>
<TouchableNativeFeedback
onPress={() => { this.onContainerViewPress(); }}
>
<WrappedComponent {...this.props} />
</TouchableNativeFeedback>
{/* <TouchableOpacity
onPress={this.props.onContainerViewPress ? () => this.props.onContainerViewPress() : null}
>
<WrappedComponent {...this.props} />
</TouchableOpacity> */}
</View>
);
}
};
}
Try to import Touchable native feedback from react native gesture handler library
import { TouchableNativeFeedback } from "react-native"
import { TouchableNativeFeedback } from "react-native-gesture-handler"
Supplementing the answer of mangei the problem could be if you import it from the wrong place. You have to import it from react-native-gesture-handler if you are inside a gesture handler (NOTE: react-navigation's TabBar itself has a PanGestureHandler in it by default). What react-native-gesture-handler does is it wraps components like ScrollView or TouchableNativeFeedback with its own implementation to be able to handle gestures inside the GestureHandler as well that are "not meant" for the GestureHandler but rather for the ScrollView or the TouchableNativeFeedback. If you're inside the gesture handler, you have to import it from react-native-gesture-handler else from react-native.