How to optimize FlatList in React Native - reactjs

Can you please tell me how can I optimize this flatlist in react native. I mean how can I do that app will render not the whole list of data but just small part of it for example 10 items, and then when the user will scroll it down it will load more of data from list?
that's the code
import React, {useState, useEffect} from 'react';
import {
SafeAreaView,
StatusBar,
StyleSheet,
Text,
View,
FlatList,
TextInput,
} from 'react-native';
import {newdata} from '../Data/newdata';
const Sample = () => {
const DATA = newdata;
const [searchText, onChangeSearch] = useState('');
const [filteredData, setFilteredData] = useState([]);
useEffect(() => {
const filtered = DATA.filter(item =>
item.title.toLowerCase().includes(searchText.toLowerCase()),
);
if (searchText === '') {
return setFilteredData(DATA);
}
setFilteredData(filtered);
}, [searchText]);
const Item = ({title}) => (
<View style={styles.item}>
<Text style={styles.title}>{title}</Text>
</View>
);
const renderItem = ({item}) => <Item title={item.title} />;
return (
<SafeAreaView style={styles.container}>
<TextInput
style={{
height: 50,
borderColor: '#919191',
borderWidth: 1,
margin: 10,
paddingLeft: 15,
borderRadius: 10,
}}
onChangeText={newText => onChangeSearch(newText)}
placeholder="Axtaris..."
/>
<FlatList
data={filteredData}
renderItem={renderItem}
keyExtractor={(item, index) => item.key}
/>
</SafeAreaView>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
marginTop: StatusBar.currentHeight || 0,
marginBottom: 75,
},
item: {
backgroundColor: '#ededed',
padding: 20,
marginVertical: 2,
marginHorizontal: 10,
borderRadius: 20,
},
title: {
fontSize: 20,
},
});
export default Sample;
P.S. newdata has about 42000 of items, and app running very slow. That is the screenshot of app

You can easily achieve that by using the initialNumToRender prop in FlatList component
How many items to render in the initial batch. This should be enough to fill the screen but not much more. Note these items will never be unmounted as part of the windowed rendering in order to improve perceived performance of scroll-to-top actions.
<FlatList
data={filteredData}
renderItem={renderItem}
keyExtractor={item => item.key}
initialNumToRender={10}
/>

Ali,
You have some features inside Flatlist in order to optimize it for example:
maxtorenderperbatch: This controls the amount of items rendered per batch, which is the next chunk of items rendered on every scroll.
Read more here
initialNumToRender: Define precise number of items that would cover the screen for every device. [Read more here][1]
Also, you can use Infinite Scroll, is very useful instead of render
the whole list you can render only amount of items, and when the user
is scrolling to the end the app load more items.
onEndReached: Called once when the scroll position gets within onEndReachedThreshold of the rendered content.
onEndReachedThreshold: How far from the end (in units of visible length of the list) the bottom edge of the list must be from the end
of the content to trigger the onEndReached callback.
Here is an example how to use it.
<FlatList
data={filteredData}
renderItem={renderItem}
keyExtractor={(item, index) => item.key}
onEndReached={ loadMoreItems }
onEndReachedThreshold={ 0.5 }
maxToRenderPerBatch={3}
initialNumToRender={5}
/>
loadMoreItems:
const loadMoreItems = ( ) => {
// Here you logic to render more items, when user scroll to the end
}

Related

react native Flatlist re rendering elements

In the following code, there is an array of 200 elements. And I am using Flatlist to render elements of the array. But renderItems function is called for first 10 elements and then for first 20 elements including first 10 elements and then for first 30 elements including first 20 elements and so on. I expect renderItems function to be called once for each item and only for the items which are displayed on the screen. What should I change in my code?
import {View, Text, FlatList} from 'react-native';
const numbers = Array.from(Array(200), (_, i) => i);
const renderItems = (item) => {
console.log('Render Items == ', item.item);
return (
<View>
<Text
style={{
fontSize: 40,
fontWeight: '400',
textAlign: 'center',
flex: 1,
}}>
{item.item}
</Text>
</View>
);
};
const App = () => {
return (
<View
style={{
flex: 1,
width: '100%',
}}>
<FlatList
data={numbers}
renderItem={renderItems}
keyExtractor={(item) => item.toString()}
/>
</View>
);
};
export default App;```

React, render different components and maintain scroll position

Suppose you have a horizontal flatlist.
When a user clicks a button in an item, you want to present a view which looks different from the flat-list item you had.
Suppose you implement it like the following
{showDetail ? (
<DetailView onPress={toggleShowDetail} />
) : (
<FlatList
data={data}
renderItem={() => (
<View>
<Button onPress={toggleShowDetail} />{' '}
</View>
)}
/>
)}
Is the scroll position of flatlist maintained when the flatlist is replaced with DetailView and replaced back?
if not, what are the approaches I can take?
I'd like to avoid using modal if possible
edit,
I'm not sure if setting style width=0 would maintain the scroll position when set width=prevSavedWidth .. but definately can try..
import _ from 'lodash'
import React, {useState} from 'react'
import {useDispatch} from 'react-redux'
import {useSelector} from 'react-redux'
import {
Text,
Image,
View,
NativeModules,
NativeEventEmitter,
TouchableOpacity,
FlatList,
} from 'react-native'
const Qnas = props => {
const flatlistRef = React.useRef(null)
const [single, setSingle] = React.useState(false)
let qnas = [
{
title: 'a',
id: 1,
},
{
title: 'b',
id: 2,
},
{
title: 'c',
id: 3,
},
{
title: 'd',
id: 4,
},
{
title: 'e',
},
{
title: 'f',
},
{
title: 'j',
},
]
const toggle = () => {
setSingle(!single)
}
const renderItem = ({item: qna, index}) => {
return (
<View style={{height: 80, width: 200}}>
<Text>{qna.title}</Text>
<TouchableOpacity onPress={toggle}>
<Text>toggle</Text>
</TouchableOpacity>
</View>
)
}
const keyExtractor = (item, index) => {
return `qna-${item.title}-${index}`
}
return (
<View style={{height: 200}}>
{single ? (
<View>
<Text>hello</Text>
<TouchableOpacity onPress={toggle}>
<Text>toggle</Text>
</TouchableOpacity>
</View>
) : (
<FlatList
horizontal
ref={flatlistRef}
data={qnas}
renderItem={renderItem}
keyExtractor={keyExtractor}
contentContainerStyle={{
flexDirection: 'column',
flexWrap: 'wrap',
}}
/>
)}
</View>
)
}
export default Qnas
the questions is not clear, but the scroll position of the page is always maintained , even if you render your components on conditional basis. if you want to make sure that you always scroll to the top of the page when you show the Detail view , you can always do window.scrollTo(0, 0 before showDetail flag as true.
Kindly provide more details for better understanding or share a code pen
you can have a handleScroll method which gets called on FlatList Scroll and saves the srollPosition in state
const handleScroll = event => {
console.log(event);
setScrollPosition(event.nativeEvent.contentOffset.x);
};
<FlatList onScroll={handleScroll} />```
and then in your toggle method have a check on if single is true set the scrollPosition of the FlatList using ref
const toggle = () => {
if(single){
this.flatlistRef.scrollToOffset({ animated: true, offset: scrollPosition }
setSingle(!single);
}
hope this helps

Select items and transfer selected items to another page

Hello I am creating an app, and am having difficulties trying to create a way where the user selects multiple images, which will then be passed on to another screen. Could I please get some help on this?
Much will be appreciated.
So, the way my app works is that, the user selects multiple items, then there should be an add button or a save button, that will get the selected items and display them to another screen. The items have a value which are Images, not text. This is purely the reason why I asked the question here because most of the React-Native tutorials include values based on text, rather than Images.
The problem I am having, is trying to figure out a way for the user to select multiple items, and clicking a save button, which will in return transfer all of the "selected items" to another screen to be display there. Much like a viewer.
import React, { Component } from 'react';
import { Text, View, StyleSheet, AppRegistry, FlatList, Image, TouchableOpacity } from 'react-native';
import flatListData from '../database';
class FlatListItem extends Component {
static navigationOptions = ({ navigation }) => ({
title: 'FirstScreen!'
})
render() {
return (
<View style={{
flex: 1,
flexDirection:'column',
}}>
<View style={{
flex: 1,
flexDirection:'row',
}}>
<View style={{
flex: 1,
flexDirection:'column',
height: 100
}}>
<TouchableOpacity onPress={() => this.props.navigation.navigate('SecondScreen')} >
<Image source={{uri: this.props.item.imageUrl}}
style={{width: 100, height: 100, margin: 5}}></Image>
</TouchableOpacity>
</View>
</View>
<View style={{
height: 1,
backgroundColor:'white'
}}>
</View>
</View>
);
}
}
class FirstScreen extends Component {
static navigationOptions = ({ navigation }) => ({
title: 'First Screen'
})
render() {
return (
<View style={{flex: 1, marginTop: 22}}>
<FlatList
data={flatListData}
renderItem={({item, index})=>{
//console.log(`Item = ${JSON.stringify(item)}, index = ${index}`);
return (
<FlatListItem item={item} index={index}>
</FlatListItem>);
}}
>
</FlatList>
</View>
);
}
}
export default example;
const styles = StyleSheet.create({
flatListItem: {
color: 'white',
padding: 10,
fontSize: 16,
}
});
Since you did not provide any sample code, so I will try to suggest a way to handle via pseudocode
You can abstract out the list of images into a centralized helper class, then you render from this helper class for user to select.
Now when user have selected one of the image, you just need to capture the ID or any unique identifier, and pass it to second screen.
On this second screen, just using this ID/unique identifier that you've received and search it from the aforementioned centralized helper class and render it.
Looks like you have two things to figure out;
one is keeping track of what items a user has selected on your image selection screen
sending the data back between screens
Looks like you are most likely using react-navigation based on your example so the simplest solution would be to take advantage of React's state and use react-navigation's parameter passing between screens.
With react-navigation you can use the second argument in navigation.navigate to pass parameters/callbacks to the other screen. So you can navigate to a screen and pass a callback to it as such.
...
this.props.navigation.navigate(
'ItemSelectionScreen',
{ onSubmit: (items) => { /* Do something with items */ } }
)
...
And here is a basic example of a selection screen with some comments to explain how it works.
import React from 'react';
import { StyleSheet, Text, TouchableOpacity, View } from 'react-native';
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: 'grey',
alignItems: 'center',
justifyContent: 'center'
}
});
class ItemSelectionScreen extends React.Component {
constructor(props) {
super(props);
this.onSubmit = () => props.navigation.getParam('onSubmit'); // Keep the passed callback once we have navigated to this screen
this.state = {
selectedItems: [] // Our initial selectedItems (empty)
};
}
handleToggle = (item, isSelected) => {
const { selectedItems } = this.state;
this.setState({ selectedItems: isSelected ? selectedItems.filter(ent => ent !== item) : [...selectedItems, item] }); // Toggle if an item is selected
};
handleSubmitAndExit = () => {
const { onSubmit } = this;
const { selectedItems } = this.state;
onSubmit(selectedItems); // Pass your selectedItems back to the other screen
this.props.navigation.goBack(); // And exit the screen
};
handleExit = () => {
this.props.navigation.goBack(); // Exit the screen without calling onSubmit
};
renderItem = (item, index) => {
const { selectedItems } = this.state;
const isSelected = selectedItems.some(ent => ent === item); // Determine if an item is selected
return (
<TouchableOpacity key={index} onPress={() => this.handleToggle(item, isSelected)}>
<Text>{`${isSelected ? 'X' : 'O'} ${item}`}</Text>
</TouchableOpacity>
);
};
render() {
return (
<View style={styles.container}>
{['item1', 'item2', 'item3'].map(this.renderItem)}
<TouchableOpacity onPress={this.handleSubmitAndExit}>
<Text>Submit and Exit</Text>
</TouchableOpacity>
<TouchableOpacity onPress={this.handleExit}>
<Text>Exit</Text>
</TouchableOpacity>
</View>
);
}
}
export default ItemSelectionScreen;
Good luck and hope this was helpful.

react native search and scroll to hit

I need some help implementing a search and scroll to hit in react native. Did a lot of searches and ended up in some dead ends (found some refs examples I couldn't get to work).
Tried building this snippet as a kick-off:
https://snack.expo.io/#norfeldt/searching-and-scroll-to
import React, { Component } from 'react';
import { Text, View, ScrollView, TextInput, StyleSheet } from 'react-native';
export default class App extends Component {
state = {
text: '41'
}
render() {
return (
<View style={styles.container}>
<TextInput
style={{height: 60, borderColor: 'gray', borderWidth: 1, borderRadius: 10, margin: 5, padding:30, color: 'black', }}
onChangeText={(text) => this.setState({text})}
value={this.state.text}
/>
<ScrollView >
{[...Array(100)].map((_, i) => {return <Text style={styles.paragraph} key={i}>{i}</Text>})}
</ScrollView>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
paddingTop: 10,
backgroundColor: '#ecf0f1',
},
paragraph: {
margin: 10,
fontSize: 18,
fontWeight: 'bold',
textAlign: 'center',
color: '#34495e',
},
});
Any help getting started would be appreciated.
My guess would be:
You could start by binding a ref of your <ScrollView/>.
// ScrollView Ref.
<ScrollView ref={(ref) => this['ScrollView'] = ref}>
...
</ScrollView>
And each of your <Text/> components (by index).
// Text Refs.
<Text ref={(ref) => this[i] = ref} style={styles.paragraph} key={i}>{i}</Text>
You could then set a submit() function.
Said function could find the ref equal to this.state.text using a try catch statement for graceful failure in edge cases.
If found; target x and y offset could be retrieved using measure()
scrollTo() could then be called to scroll to the target component.
// Scroll To Query.
submit = () => {
try {
const { text } = this.state // Text.
const target = this[text] // Target.
// Locate Target.
target.measure((framex, framey, width, height, x, y) => {
// Scroll To Target.
this.ScrollView.scrollTo({x, y, animated: true})
})
} catch (error) {
return console.log(error)
}
}
First of all, I highly recommend you to use FlatList instead of ScrollView. There are a few reasons for this:
FlatList has much more optimized performance in comparison with ScrollView (in scroll view all items are rendered at once, regardless of the fact if they are visible on screen or not)
Moreover, the handling scrolling and rendered items is much simpler in FlatList, you do not need to know anything about x, y axis and pixels, you just work with indexes.
in order to have a comprehensive comparison between these two methods you may look at:
http://matthewsessions.com/2017/05/15/optimizing-list-render-performance.html
Now back to your question, as I said I suggest you to use FlatList, then everything will be as simple as a piece of a cake.
You can find modified example of your expo in:
https://snack.expo.io/HkMZS1SGz
The changes that you need to make in your code, include:
Instead of ScrollView use, FlatList, so change this:
<FlatList
ref={ref => {this.flatListRef = ref;}}
data={new Array(100).fill(0).map((item, index) => index)}
renderItem={({ item }) => (
<Text style={styles.paragraph}>
{item}
</Text>
)}
/>
If you are not already familiar with FlatList, you need to know, the data is added in data prop as an array (I added an array of 100 numbers), and the way it is rendered is given to FlatList as renderItemprop (I added the text with the same styling as you did).
Moreover, note that you do not need to pass ref to <Text>, because FlatList already knows about items that it contains. You just need to add a ref to the FlatList itself:
ref={ref => {this.flatListRef = ref;}}
Now when ever you want to make and scrolling, you can simple call scrollToIndex method of the FlatList, for example write a method called scrollHandler:
// Scroll to Query
scrollHandler = (itemIndex)=>{
this.flatListRef.scrollToIndex({animated: true, index: itemIndex});
}
just pay attention that,flatListRef is the name of the ref assigned to the FlatList.
now, when you want to perform scroll action, you can simply call this method. Forexample, modify your text input to:
<TextInput
style={{height: 60, borderColor: 'gray', borderWidth: 1,
borderRadius: 10, margin: 5, padding:30, color: 'black', }}
onChangeText={(text) => this.setState({text})}
value={this.state.text}
onSubmitEditing={()=>this.scrollHandler(this.state.text)}
/>
Steps
Remember every item's position with onLayout.
scrollTo() position when text input, and only if item found.
Code
const styles = StyleSheet.create({
textinput: {
borderBottomColor: 'purple',
textAlign: 'center',
borderBottomWidth: 2,
height: 40,
marginTop: 20,
},
text: {
textAlign: 'center',
fontSize: 16,
margin: 10,
}
});
export class App extends Component {
data = [];
datapos = {};
scrollref = null;
constructor(props) {
super(props);
/// make 100s example data
for (var i =0; i<100; ++i)
this.data.push(i);
this.state = {
inputvalue: '0'
}
}
render() {
return (
<View style={{flex: 1}}>
<TextInput style={styles.textinput}
value={this.state.inputvalue}
onChangeText={(text) => {
this.setState({inputvalue: text});
let y = this.datapos[+text];
y !== undefined && this.scrollref.scrollTo({ y, animated: true });
}}
/>
<ScrollView
ref={(ref) => this.scrollref = ref}
>
{
this.data.map( (data) => (
<Text style={styles.text}
key={data}
onLayout={(layout) => this.datapos[data] = layout.nativeEvent.layout.y}
>{data}</Text>
))
}
</ScrollView>
</View>
)
}
}
Result:

ScrollView RTL in react native

The following code is my ScrollView in a react native project:
<ScrollView
ref={(scrollView) => { this._scrollView = scrollView; }}
horizontal={true}
showsHorizontalScrollIndicator={false}
showsVerticalScrollIndicator={false}
directionalLockEnabled={true}
bounces={false}
scrollsToTop={false}
>
Now it moves from left to right, How it could move from right to left in first loading?
For RTL setting you should write your code like the below sample:
import React, { useRef } from 'react';
import { ScrollView, StyleSheet } from 'react-native';
const RTLScrollView = () => {
const scrollRef = useRef();
const scrollToEnd = () => scrollRef.current.scrollToEnd({ animated: false });
return (
<ScrollView
horizontal
ref={scrollRef}
showsHorizontalScrollIndicator={false}
onContentSizeChange={scrollToEnd}
contentContainerStyle={styles.contentContainerStyle}
>
~~~
~~~
~~~
</ScrollView>
);
}
const styles = StyleSheet.create({
contentContainerStyle: {
flexDirection: 'row-reverse'
}
});
export default RTLScrollView;
Hint: I don't use your other ScrollView settings like bounces={false}, If you want, put it in your code, my answer is just a sample.
This is truly an annoying Bug in React Native , ScrollView+RTL=Silly Bug.
Though , there are multiple hacks you can adapt , I did this to overcome the bug :
I reversed the data array I am using.
Used : onContentSizeChange event handler to trigger the
scrollToEnd({ animated: false }) function on ScrollView
you can try invertible-scroll-view that supports horizontal and vertical scroll view
As #SlashArash has mentioned you can use the react-native-invertible-scroll-view.
Here's an example:
import React, { Component } from 'react';
import { View, Text, ScrollView} from 'react-native';
import InvertibleScrollView from 'react-native-invertible-scroll-view';
export default class Demo extends Component {
constructor(props) {
super(props);
this.scrollView = null;
}
render() {
let categories = ['one', 'two'];
categories = categories.map((category, index) => {
return (
<Text>{category}</Text>
)
});
return (
<View style={{
flex: 1,
}}>
<InvertibleScrollView inverted
ref={ref => { this.scrollView = ref }}
onContentSizeChange={() => {
this.scrollView.scrollTo({y: 0, animated: true});
}}
horizontal={true}
showsHorizontalScrollIndicator={false}
>
{categories}
</InvertibleScrollView>
</View>
)
}
}
I had to build an autocomplete with an RTL scrolling. This proved to be tricky because the scroll to end solution caused a lot of flickering. I found the best way to make anything RTL is using the transform style. If you use transform, it is important that you apply the transform to each item in the list as well. Otherwise, you will have mirrored text. This solution also works if you want to invert top/bottom, just change transform scaleY instead of x.
Here is my ScrollView:
const renderList = () => {
return itemList.map(item => (
<Item
key={item.id}
address={item}
style={{ transform: [{ scaleX: -1 }] }}
/>
));
};
....
<ScrollView
contentContainerStyle={styles.scrollViewContentContainer}
horizontal
keyboardShouldPersistTaps="always"
ref={scrollView}
style={styles.scrollView}
>
{renderList()}
</ScrollView
Here is the corresponding ScrollView style:
const styles = StyleSheet.create({
scrollView: {
marginRight: 10,
transform: [{ scaleX: -1 }]
},
scrollViewContentContainer: {
flexGrow: 1,
justifyContent: 'flex-end',
}
});

Resources