How to Scroll Right To Columns in React Native FlatList (2018) - react-native

I am seeking a way to scroll a viewport over a table like this, except that every cell is exactly the same size:
I am currently using FlatList's numColumns parameter to make a table and scroll the viewport over that table.
Here is a Snack example - RegularGridExample:
import React from 'react';
import { FlatList, Text, View } from 'react-native';
const numRows = 10,
numColumns = 10,
width = 100,
height = 100,
cells = [...Array(numRows * numColumns)].map((_, cellIndex) => {
const rowIndex = Math.floor(cellIndex / numRows),
colIndex = cellIndex % numColumns;
return {
key: `${colIndex},${rowIndex}`,
rowIndex,
colIndex,
styles: {
width,
height,
backgroundColor: 'green',
borderColor: 'black',
borderWidth: 1,
},
};
});
export default class RegularGridExample extends React.Component {
render() {
return (
<FlatList
data={cells}
renderItem={this.renderItem}
numColumns={numColumns}
horizontal={false}
columnWrapperStyle={{
borderColor: 'black',
width: numColumns * width,
}}
/>
);
}
renderItem = ({ item: { styles, rowIndex, colIndex } }) => {
return (
<View style={styles}>
<Text>r{rowIndex}</Text>
<Text>c{colIndex}</Text>
</View>
);
};
}
This example will correctly scroll to reveal the rows below the viewport, but it will not scroll to reveal the columns beyond the viewport. How can I enable scrolling the viewport to reveal a FlatList's columns?
Update 1
I do not think this can be easily solved with nested FlatLists, which is the first thing I tried before using the numColumns approach above. The use case here is shifting the viewport over a grid that's larger than the viewport, not just scrolling one row within the viewport.
Update 2
I'm seeking a virtualized solution. While the wireframe above uses text, the use case I'm actually interested in is browsing a tile server navigating over portions of a large 50MB+ image. It will be too slow to load all of them into a scroll view.
Unrelated Stack Overflow Posts
React Native ScrollView/FlatList not scrolling - this is about adding flex to the viewport to enable scrolling along the major axis of the FlatList, which is already working in the example above. My concern is scrolling along the crossAxis.
React native flatlist not scrolling - it is unclear what the expected and actual behavior is here
How can I sync two flatList scroll position in react native - here, the poster is seeking to simulate masonry layout; I'm not doing anything so fancy

This can't be done using the FlatList method, since numColumns is used which explicitly sets horizontal={false}, hence disabling the scrolling horizontal direction.
Here's a workaround by using nested ScrollViews
export default class RegularGridExample extends React.Component {
render() {
const generatedArray = [1,2,3,4,5,6]
return (
<ScrollView horizontal>
<ScrollView >
{generatedArray.map((data, index) => {
return <View style={{flexDirection: 'row'}} >
{generatedArray.map((data, index) => {
return <View style={{height: 100, width: 100, backgroundColor: 'red', borderWidth: 1, borderColor: 'black'}} />
})}
</View>
})}
</ScrollView>
</ScrollView>
);
}
}

Related

React Native SectionList Performance Issue: Extremely Low JS Frame Rate While Scrolling React Native SectionList

Scrolling a SectionList results in unreasonably low JS frame rate dips (~30) for simple cell renders (a single text label) and gets much worse if cell contents are any more complex (several text labels in each cell will result in single digit JS frame rate dips).
The user facing-problem manifested by the low JS frame rate is very slow response times when a user taps anything after scrolling (it can be ~5 seconds delay).
Here's an example repo which creates both a FlatList and a SectionList each with 10,000 items on a single screen (split laterally across the middle of the screen). It's a managed Expo app (for ease of reproduction) and it's written in Typescript (because I'm used to that). The readme describes setup steps if you need them. Run the app on a physical device and turn on React Native's performance monitor to view the JS frame rate. Then scroll each of the lists as fast as you can to view the differing effects on the JS frame rate while scrolling a FlatList vs a SectionList.
Here's the entire source (from that repo's App.tsx file; 61 lines) if you prefer to bootstrap it yourself, or eyeball it here without going to GitHub:
import React from "react"
import { FlatList, SectionList, Text, View } from "react-native"
const listSize = 10_000
const listItems: string[] = Array.from(
Array(listSize).keys(),
).map((key: number) => key.toString())
const alwaysMemoize = () => true
const ListItem = React.memo(
({ title }: { title: string }) => (
<View style={{ height: 80 }}>
<Text style={{ width: "100%", textAlign: "center" }}>{title}</Text>
</View>
),
alwaysMemoize,
)
const flatListColor = "green"
const flatListItems = listItems
const sectionListColor = "blue"
const sectionListItems = listItems.map(listItem => ({
title: listItem,
data: [listItem],
}))
const approximateStatusBarHeight = 40
const App = () => {
const renderItem = React.useCallback(
({ item }: { item: string }) => <ListItem title={item} />,
[],
)
const renderSectionHeader = React.useCallback(
({ section: { title } }: { section: { title: string } }) => (
<ListItem title={title + " section header"} />
),
[],
)
return (
<View style={{ flex: 1, paddingTop: approximateStatusBarHeight }}>
<FlatList
style={{ flex: 1, backgroundColor: flatListColor }}
data={flatListItems}
renderItem={renderItem}
keyExtractor={item => item}
/>
<SectionList
style={{ flex: 1, backgroundColor: sectionListColor }}
sections={sectionListItems}
renderItem={renderItem}
renderSectionHeader={renderSectionHeader}
keyExtractor={item => item}
/>
</View>
)
}
export default App
Have I missed some performance optimization/am I doing something wrong? Or is this React Native's expected performance?
Another question as a side note: If I don't set the height of each of the cells to something reasonably tall (eg 80 in the example above) and just let the height adjust to the height of the contained text (14pt I believe), the JS frame rate dip for both types of lists becomes quite bad; 20 JS frames or less.
I'll also note here that the performance tests shown in the screen shots were done on an iPhone 13 mini.

React native, view got cut off when rendered in flat list

So, I'm currently working on the react native project, I trying to add a tooltip component which once user tap the item in Flatlist. it will triggered this tooltip that have several options. The problem is now it got cut off even I set the 'position' to be 'absolute' with x,y position.
Is there anyway I can overcome this problem? I tried with the zIndex as well, but still not work out.
Here is the tooltip component that I implemented.
export function Tooltip({
children,
x,
y,
height,
width,
isVisible = false,
component,
}: TooltipProps) {
const [myWidth, setW] = useState(0)
const [myHeight, setH] = useState(0)
function onLayout({
nativeEvent: {
layout: {width, height},
},
}: LayoutChangeEvent) {
setW(width)
setH(height)
}
return (
<View>
{children}
{isVisible && (
<View
onLayout={onLayout}
style={{
elevation: 5,
borderWidth: 1,
backgroundColor: 'white',
position: 'absolute',
top: (height - componentHeight * 2) / 2 + y,
left: (width - componentWidth) / 2 + x,
}}>
{component}
</View>
)}
</View>
)
}
Here is the image in the app, (I need to blur out items there, sorry for inconvenience)
Tty to make parent view is SafeAreaView.
return(<SafeAreaView><YourComponent/></SafeAreaView>)
May be that can help.

React Native: Correct scrolling in horizontal FlatList with Item Separator

ReactNative: v0.52.0
Platform: iOS
My FlatList code:
<FlatList
horizontal
pagingEnabled={true}
showsHorizontalScrollIndicator={false}
legacyImplementation={false}
data={this.props.photos}
renderItem={item => this.renderPhoto(item)}
keyExtractor={photo => photo.id}
ItemSeparatorComponent={this.itemSeparatorComponent}
/>
Item separator code:
itemSeparatorComponent = () => {
return <View style = {
{
height: '100%',
width: 5,
backgroundColor: 'red',
}
}
/>
}
And finally FlatList item component:
renderPhoto = ({ item, index }) => {
return (
<View style = {{ width: SCREEN_WIDTH, height: 'auto' }}>
<FastImage
style = { styles.photo }
resizeMode = { FastImage.resizeMode.contain }
source = {{ uri: item.source.uri }}
/>
</View>
)
}
But when scrolling, the FlatList makes an offset to the separator but not to the left edge of item:
And with each new element the FlatList adds the width of the all previous separators to offset:
How to make the FlatList component consider the width of the separator component in horizontal scrolling and make proper offset?
I had the same use-case. For anyone looking for a solution, here it is.
Step 1) Don't use ItemSeparatorComponent prop. Instead, render it inline in your renderItem component.
Step 2) (Key-point). Specify the width and height in the style prop of the FlatList. The width, in your case, should be SCREEN_WIDTH + 5.
Then Flatlist will automatically move the entire screen (photo + separator) away when pagination is enabled. So now your code should be like so:-
<FlatList
horizontal
pagingEnabled={true}
showsHorizontalScrollIndicator={false}
legacyImplementation={false}
data={this.props.photos}
renderItem={item => this.renderPhoto(item)}
keyExtractor={photo => photo.id}
style={{width: SCREEN_WIDTH + 5, height:'100%'}}
/>
Render photo code:-
renderPhoto = ({ item, index }) => {
return (
<View style = {{ width: SCREEN_WIDTH + 5, height: 'auto',
flexDirection:'row'}}>
<FastImage
style = { styles.photo }
resizeMode = { FastImage.resizeMode.contain }
source = {{ uri: item.source.uri }}
/>
{this. itemSeparatorComponent()}
</View>
)}
Item separator code:
itemSeparatorComponent = () => {
return <View style = {
{
height: '100%',
width: 5,
backgroundColor: 'red',
}
}
/>
}
If you still can't figure it out, then look at this component:
https://github.com/zachgibson/react-native-parallax-swiper
Try to go into the implementation, you will see that this guy has provided width and height to the Animated.ScrollView.
https://github.com/zachgibson/react-native-parallax-swiper/blob/master/src/ParallaxSwiper.js
Line number: 93 - 97
The top-level view you're returning in the renderPhoto function has a width of SCREEN_WIDTH, yet the ItemSeparatorComponent, which renders in between each item, is taking up a width of 5 as per your style definition. Consequently, for each additional item you scroll to, that initial offset will become 5 more pixels on the left.
To fix this, you can either remove the ItemSeparatorComponent completely, (as you already have pagingEnabled set to true), or set the width of the top-level view returned in renderPhoto equal to SCREEN_WIDTH - 2.5. That way you'll see half of the item separator on the right edge of one photo and the other half on the left edge of the next photo.
Actually, one other possible solution could be to remove the item separator, set the renderPhoto View's width to SCREEN_WIDTH + 5, and then include these additional properties inside the style: {paddingRight: 5, borderRightWidth: 5, borderRightColor: 'red'}. That way the red separator won't be visible until scrolling left and right, because of the pagingEnabled property.

Horizontal FlatList React Native with different height of items

I have a little problem with the FlatList Component in react native. In the FlatList are items with different height. Here is a little example of what i have:
example FlatL
The first problem is that all items are positioned at the top. I want to position all items at the bottom.
The second problem is that the height of the FlatList is always the height of the biggest item. So you can also scroll to another item in the white area of a small item...
here my code:
import React from "react";
import {
Text,
View,
Dimensions,
StyleSheet,
ListView,
TouchableOpacity,
Animated,
Image,
FlatList
} from "react-native";
import glamorous, { ThemeProvider } from "glamorous-native";
import theme from "../theme";
const { height, width } = Dimensions.get("window");
const cards = [
{
id: 1,
color: "red",
height: 400
},
{
id: 2,
color: "blue",
height: 300
},
{
id: 3,
color: "yellow",
height: 200
}
];
class Test extends React.Component {
render() {
return (
<View style={{ bottom: 0 }}>
<FlatList
ref={elm => (this.flatList = elm)}
showsHorizontalScrollIndicator={false}
data={cards}
pagingEnabled={true}
horizontal={true}
keyExtractor={item => item.id}
renderItem={({ item }) => (
<View
style={{
height: item.height,
width: width,
backgroundColor: item.color
}}
/>
)}
/>
</View>
);
}
}
export default Test;
Has anyone a solution?
You can change the position of each item by setting the contentContainerStyle property of the FlatList itself. But the height of the FlatList will always have to be the height of the largest component inside it.
I solved this with onViewableItemsChanged prop
viewabilityConfig={{itemVisiblePercentThreshold: 50}}
onViewableItemsChanged={this.onViewableItemsChanged}
onViewableItemsChanged = ({ viewableItems }) => {
let index = viewableItems[0].index;
this.setState({ indexOfImages: index, heightOfImages: SCREEN_WIDTH * (viewableItems[0].item.height / viewableItems[0].item.width) });
}
We have width and height of the items in its data so I'm taking width and height of the visible item on the screen and do some calculations and I'm using this.state.heightOfImages in flatlist height like this
height: this.state.heightOfImages == undefined ? SCREEN_WIDTH * (images[0].height / images[0].width) : this.state.heightOfImages
You can just wrap the content of each card in another <View> element with styles={{ minHeight: 200, maxHeight: 300 }} and it will work fine!

React Native <ScrollView> persistent scrollbar

After perusing the React Native Documentation I couldn't seem to find out how to make a <ScrollView> have a persistent scrollbar that doesn't fade out. How would I achieve that?
iOS
The underlying iOS native component, UIScrollView (technically, RCTEnhancedScrollView), doesn't support keeping the scroll indicators visible. For this reason, the React Native wrapper around it won't either.
There is a hack to get this working with the native component (see this answer for one approach). To accomplish this in React Native, you'd need to implement this hack on the native side, and then either create your own Native Module or fork React Native and modify their ScrollView component.
That said, the iOS Scroll View interface guidelines discourage this, so you may want to leave the indicators' behavior alone.
Android
A few approaches:
set <item name="android:overScrollMode">always</item>,
set android:fadeScrollbars="false" in XML, or
set ScrollView.setScrollbarFadingEnabled(false) in Java (e.g. in your custom native bridge code)
This is similarly discouraged as nonstandard UI unless you have a strong reason for it.
Adding answer since none of the above worked for me.
Android now has the persistentScrollbar props.
iOS does not support this. So I created a JS solution that can be used as follows:
<SBScrollView persistentScrollbar={true}>...</SBScrollView>
Basically, this functional component will use persistentScrollbar when on Android, while add a bar when we are on iOS. It is not smooth for now, but it is functional.
// #flow
import React, {useState} from 'react';
import {Platform, View, ScrollView} from 'react-native';
type Props = {|
persistentScrollbar?: boolean,
children?: React$Node,
|} & View.propTypes;
export default function SBScrollView({
persistentScrollbar = false,
children,
...other
}: Props) {
const [nativeEvent, setNativeEvent] = useState();
if (Platform.OS === 'android' || !persistentScrollbar) {
// Abdroid supports the persistentScrollbar
return (
<ScrollView persistentScrollbar={persistentScrollbar} {...other}>
{children}
</ScrollView>
);
}
const top = nativeEvent
? nativeEvent.contentOffset.y +
(nativeEvent.contentOffset.y / nativeEvent.contentSize.height) *
nativeEvent.layoutMeasurement.height
: 0;
// iOS does not support persistentScrollbar, so
// lets simulate it with a view.
return (
<ScrollView
scrollEventThrottle={5}
showsVerticalScrollIndicator={false}
onScroll={event => setNativeEvent(event.nativeEvent)}
{...other}>
{children}
<View
style={{
position: 'absolute',
top,
right: 4,
height: 200,
width: 4,
borderRadius: 20,
backgroundColor: 'gray',
}}
/>
</ScrollView>
);
}
I hope this can help others.
I was looking for a solution but I didn't find nothing, then I created a solution, I hope can help you with it.
I created a view View with height and width and put it over my scrollview, after that I used the Props of scrollview like onMomentumScrollBegin, onMomentumScrollEnd, onContentSizeChange and onScroll
after that I make a condition with a boolean variable, if this variable is false, the View is visible, if is false the View is hide, How do I active this variable? with the Prop onMomentumScrollBegin that detect when you use the scrollView and the same way to set the variable in false with onMomentumScrollEnd that detects when the scroll ends.
The Prop onContentSizeChange allows me to get the height and width of my scrollview, this values I used to calculate where would be set the scrollbar/scrollIndicator
and finally with the Prop onScroll I get the position.
the example:
<ScrollView
onMomentumScrollBegin={() => {this.setvarScrollState()}}
onMomentumScrollEnd={() => {this.setvarScrollStateRev()}}
scrollEventThrottle={5}
onContentSizeChange={(w, h) => this.state.hScroll = h}
showsVerticalScrollIndicator={false}
onScroll={event => { this.state.wScroll = event.nativeEvent.contentOffset.y }}
style={{ marginVertical: 15, marginHorizontal:15, width: this.state.measurements3.width}}>
{
Mydata.map((value, index) => {
return <TouchableOpacity>
<Text>{ value.MyDataItem }</Text>
</TouchableOpacity>
})
}
the functions:
setvarScrollState() {
this.setState({VarScroll: true});
}
setvarScrollStateRev() {
this.setState({VarScroll: false});
}
and the variable
this.state = {VarScroll: false}
Then my condition is
!this.state.VarScroll ?
<View
style = {{
marginTop: 200*(this.state.wScroll / this.state.hScroll),
marginLeft:338.5,
height: 35,
width: 2,
backgroundColor: 'grey',
position:'absolute'
}}
/>
: null
Why 200? because is the maximum value that my marginTop can set
Check the picture
Final note:
the scrollView have to be inside a View with the another View (scrollbar)
something like this
<View>
{/*---- ScrollBar and conditions----*/}
<View>
<View>
<ScrollView>
</ScrollView>
</View>