I am rendering a list in React Native which currently has about 900 list items. I'm mapping through the list and rendering one component for each item. It currently takes about 3 seconds for React to do this which is unacceptable - I would like it to be near instant. Props are passed to the list item component from the redux store and the list items are nested inside the React Native ScrollView Component.
How can I can I increase the performance of rendering these components so there is not such a huge lag?
Here is my Contacts component:
class Contacts extends Component {
renderRegisteredUsers = (contacts) => {
return contacts.items.map((contact, index) => (
<ContactListItem
key={index}
firstName={contact.user.address_book_name}
surname={''}
phoneNumber={contact.user.phone}
isRegisteredUser={true}
ccId={contact.user.id}
/>
))
}
renderContacts = (contacts) => {
if (contacts) {
return contacts.map((contact, index) => (
<ContactListItem
key={index}
firstName={contact.firstName}
surname={contact.surname}
phoneNumber={contact.phoneNumber}
isRegisteredUser={false}
/>
))
} else {
return (
<>
<Loader />
</>
)
}
}
render() {
return (
<>
<ScrollView>
<Text style={{ fontSize: 22 }}>
Your Contacts Using Fleeting
</Text>
{this.renderRegisteredUsers(this.props.user.registeredContacts)}
<Text style={{ fontSize: 22 }}>
Phone Contacts
</Text>
{this.renderContacts(this.props.user.parsedContacts)}
</ScrollView>
</>
)
}
}
const mapStateToProps = (state) => {
const { user } = state;
return { user }
};
export default connect(mapStateToProps)(Contacts);
And my ContactListItem component:
class ContactListItem extends Component {
constructor(props) {
super(props)
}
handleOnClick = () => {
this.props.calleeId(this.props.ccId)
Actions.TimeInput();
}
render() {
return (
<View style={{ margin: 20, display: "flex", flexDirection: "column", justifyContent: "space-between" }}>
<Text>
{this.props.firstName + ' ' + this.props.surname + ' ' + this.props.phoneNumber}
</Text>
<Icon name="adduser" size={40} color="green" style={{ alignSelf: "flex-end" }} onPress={this.handleOnClick} />
</View>
)
}
}
const mapDispatchToProps = {
calleeId,
};
export default connect(null, mapDispatchToProps)(ContactListItem);
Thanks in advance.
1- You can use PureComponent instead of Component. PureComponent re-renders only when its props change and not re-rendering on each parent re-render. More Information: https://reactjs.org/docs/react-api.html#reactpurecomponent
2- Use unique keys when you're mapping on your items.
3- You can use FlatList instead of ScrollView. It supports Scroll Loading. You can set a number of initial numbers and render the others on scroll. More Information: https://facebook.github.io/react-native/docs/flatlist
const renderItem = ({ item }) => (<Text key={item.key}>{item.key}</Text>);
const getItemLayout = (data, index) => (
{length: ITEM_HEIGHT, offset: ITEM_HEIGHT * index, index}
);
const items = [{ key: 'first' }, { key: 'second'}, ...+1000];
function render () => (
<FlatList
data={items}
renderItem={renderItem}
getItemLayout={getItemLayout}
initialNumToRender={5}
maxToRenderPerBatch={10}
windowSize={10}
/>
);
Look into using pagination instead- https://www.npmjs.com/package/react-paginate, https://www.npmjs.com/package/react-native-pagination. If your contacts are coming from an api request, you will have to look into paginating the requests too.
import React, { Component } from 'react';
import {AppRegistry,StyleSheet,View,FlatList,} from 'react-native';
import ContactItem from './Pages/widgets/ContactItem'; // https://github.com/garrettmac/react-native-pagination/blob/master/ReactNativePaginationExample/Pages/widgets/ContactItem.js
import faker from 'faker';//assuming you have this.
import _ from 'lodash';
import Pagination,{Icon,Dot} from 'react-native-pagination';//{Icon,Dot} also available
export default class ReactNativePaginationExample extends Component {
constructor(props){
super(props);
this.state = {
items: this.props.contacts,
};
}
//create each list item
_renderItem = ({item}) => {
return (<ContactItem index={item.id}
onPressItem={this.onPressItem.bind(this)}
name={item.name}
avatar={item.avatar}
description={item.email}
tag={item.group}
createTagColor
/>)
};
//pressed an item
onPressItem = (item) => console.log("onPressItem:item ",item);
_keyExtractor = (item, index) => item.id;
onViewableItemsChanged = ({ viewableItems, changed }) =>this.setState({viewableItems})
render() {
return (
<View style={[s.container]}>
<FlatList
data={this.state.items}
ref={r=>this.refs=r}//create refrence point to enable scrolling
keyExtractor={this._keyExtractor}//map your keys to whatever unique ids the have (mine is a "id" prop)
renderItem={this._renderItem}//render each item
onViewableItemsChanged={this.onViewableItemsChanged.bind(this)}//need this
/>
<Pagination
// dotThemeLight //<--use with backgroundColor:"grey"
listRef={this.refs}//to allow React Native Pagination to scroll to item when clicked (so add "ref={r=>this.refs=r}" to your list)
paginationVisibleItems={this.state.viewableItems}//needs to track what the user sees
paginationItems={this.state.items}//pass the same list as data
paginationItemPadSize={3} //num of items to pad above and below your visable items
/>
</View>
)
}
};
const s = StyleSheet.create({
container: {
flex: 1,
// backgroundColor:"grey",//<-- use with "dotThemeLight"
},
});
AppRegistry.registerComponent('ReactNativePaginationExample', () => App);
Related
i'm currently trying to implement a horizontal FlatList. I'm aware that i can render list of items pretty easly inside the renderItem by just looping it through... but can i actually pass a custom component inside ?
This is my array filled with custom components i created:
const arrayOfCustomComponents = [<Home/>,<News/>,<History/>,<Stats/>,<Settings />];
Given this array, can i pass each index inside renderItem to be rendered ?
<Animated.FlatList
data={data}
keyExtractor={item => item.key}
horizontal
showsHorizontalScrollIndicator={false}
pagingEnabled
bounces={false}
renderItem={({item}) =>{
return <View>
{arrayOfCustomComponents[item.key]}
</View>
}}
/>
You cannot access an array elements like you've done in your example. The keys of an array are 0, 1, 2, ....
What you can do instead is to store your components in an object like that:
const DATA = [
{
id: 'comp1',
description: 'This is component 1',
},
{
id: 'comp2',
description: 'This is component 2',
}
];
const Comp1 = () => {
return (
<View><Text>This is component 1</Text></View>
)
}
const Comp2 = () => {
return (
<View><Text>This is component 2</Text></View>
)
}
const mapOfComponents = {
comp1: <Comp1 />,
comp2: <Comp2 />
};
function App() {
return (
<View>
<FlatList
data={DATA}
renderItem={({ item }) => {
return (
mapOfComponents[item.id]
)
}
}
keyExtractor={item => item.id}
/>
</View>
);
}
My entire goal was to navigate from a screen while changing states in the screen I am navigating to. I have successfully done that in a minimal working example, however in my overall project, the screen I am navigating to needs to be passed the state through a couple levels.
I have two examples. In the first example(You must run the examples in IOS or android, you can see what I need to achieve, everything works as it should. You can move from screen3 to the home page and the states change along with the slider button moving.
In the second example, you can see right off the bat I have an error due to my attempt at passing states the same way I do in the original example however there is one more level I need to pass through in this example. You can see by removing line 39 in this demo, it removes the error so obviously I am not passing states correctly. I need to pass states from Home to Top3 to Slider
Here is example 1 and here is example 2 while I have also provided some code below that highlights the differences where the error occurs in the two examples.
Any insight at all is appreciated more than you know! Thank you.
Example1 -> you can see I directly render the slider button which causes zero issues.
const Home = ({ route }) => {
const [isVisile, setIsVisible] = React.useState(true);
const [whichComponentToShow, setComponentToShow] = React.useState("Screen1");
React.useEffect(() => {
if(route.params && route.params.componentToShow) {
setComponentToShow(route.params.componentToShow);
}
}, [route.params]);
const goToMap = () => {
setComponentToShow("Screen2");
}
const goToList = () => {
setComponentToShow("Screen1");
}
return(
<View style={{backgroundColor: '#d1cfcf' ,flex: 1}}>
{whichComponentToShow === 'Screen1' && <ListHome />}
{whichComponentToShow === 'Screen2' && <MapHome />}
<View style={{position: 'absolute', top: 0, left: 0, right: 1}}>
<Slider
renderMap={goToMap}
renderList={goToList}
active={route.params && route.params.componentToShow==='Screen2'|| false}
/>
</View>
</View>
);
}`
Example2 -> You can see I render Slider in a file called Top3, I am struggling to pass these states from Home to Top3 to Slider.
const [isVisile, setIsVisible] = React.useState(true);
const [whichComponentToShow, setComponentToShow] = React.useState("Screen1");
React.useEffect(() => {
if(route.params && route.params.componentToShow) {
setComponentToShow(route.params.componentToShow);
goToMap()
}
}, [route.params]);
const goToMap = () => {
setComponentToShow("Screen2");
}
const goToList = () => {
setComponentToShow("Screen1");
}
return(
<View style={{backgroundColor: '#d1cfcf' ,flex: 1}}>
{whichComponentToShow === 'Screen1' && <ListHome />}
{whichComponentToShow === 'Screen2' && <MapHome />}
<View style={{position: 'absolute', top: 0, left: 0, right: 1}}>
<Top3
renderMap={goToMap}
renderList={goToList}
active={route.params && route.params.componentToShow==='Screen2'|| false}
/>
</View>
</View>
);
}
Top3
export default class Top3 extends React.Component {
goToMap = () => {
this.props.renderMap();
};
goToList = () => {
this.props.renderList();
};
render() {
return (
<View>
<Slider renderMap={this.goToMap.bind(this)}
renderList={this.goToList.bind(this)}
active={active}/>
</View>
);
}
}
from your examples, I think you are not extracting active from props properly.
here is the demo working code your example2 code https://snack.expo.dev/4atEkpGVo
here is the sample code for component Top3
export default class Top3 extends React.Component {
goToMap = () => {
this.props.renderMap();
};
goToList = () => {
this.props.renderList();
};
render() {
const {active=false} = this.props;
return (
<View>
<Slider renderMap={this.goToMap.bind(this)}
renderList={this.goToList.bind(this)}
active={active}/>
</View>
);
}
}
if you want to share states between multiple screens, then you might want to use global stores like react context api or redux instead of passing states to each screen that would be simple
I used the ScrollView's onMomentumScrollEnd handler to determine the current page based on the contentOffset in the recyclerlistview Component.
const [currentPage, setCurrentPage] = useState(0);
const onScrollEnd = (e) => {
let contentOffset = e.nativeEvent.contentOffset;
let viewSize = e.nativeEvent.layoutMeasurement;
let pageNum = Math.floor(contentOffset.x / viewSize.width);
setCurrentPage(pageNum);
};
return (
<View style={{ flex: 1 }}>
<RecyclerListView
rowRenderer={({ item }, index) => <Page item={item} id={index} />}
dataProvider={list}
layoutProvider={layoutProvider}
onMomentumScrollEnd={onScrollEnd}
isHorizontal
pagingEnabled
/>
<View style={{ flex: 0.1, backgroundColor:"gold" }}>
<Text>{`current page: ${currentPage}`}</Text>
</View>
</View>
);
I want to display the current Page inside a Text component, but when currentPage state changes in onMomentumScrollEnd handler the whole app is re-render. I need to re-render only the Text Component, any suggestion for that.
I find a solution. the easiest way is to create a class component and put the displayed component in it, then I reference it and change the current page with ref hook.
class DisplayCurrentPage extends React.Component {
contractor(prop){
super(prop)
state:{currentPage:0}
}
updateCurrentPage = (index) => {this.setState({CurrentPage: index})}
render(){
return(
<View style={{ flex: 0.1, backgroundColor:"gold" }}>
<Text>{`current page: ${this.state.currentPage}`}</Text>
</View>
);
}
};
const reference = createRef();
const onScrollEnd = (e) => {
let contentOffset = e.nativeEvent.contentOffset;
let viewSize = e.nativeEvent.layoutMeasurement;
let pageNum = Math.floor(contentOffset.x / viewSize.width);
reference.current.updateCurrentPage(pageNum);
};
return (
<View style={{ flex: 1 }}>
<RecyclerListView
rowRenderer={({ item }, index) => <Page item={item} id={index} />}
dataProvider={list}
layoutProvider={layoutProvider}
onMomentumScrollEnd={onScrollEnd}
isHorizontal
pagingEnabled
/>
<DisplayCurrentPage ref={reference} />
</View>
);
When the page is changed, only DisplayCurrentPage is re-render.
You can use React.memo which is an alternative to shouldComponentUpdate for functional components. It tells React when to re-render the component based on prev and next props.
const Item = React.memo(({ item }) => {
return (
<View>
........
</View>
);
});
i'm trying to access the code and name value from within my child component which is a flatlist and access the select data in the parent component:
My flatList is as follows & it house a child component on which it will render items to. But however i get undefind or maybe i've used the wrong approach
const CustomerView = ({ code, name, email, balance, buttonPress }: any) => {
return (
<View style={styles.body}>
<TouchableOpacity onPress={buttonPress}>
<Text>Code: {code}</Text>
<Text>Name: {name}</Text>
<Text>E-Mail: {email}</Text>
<Text>Balance: {balance}</Text>
</TouchableOpacity>
</View>
);
};
And below is my flatList component which will render the above componet when data gets passed through
const CustomerFlatList = ({
customerData,
onPressSelectCustomer,
}: any) => {
return (
<View style={styles.body}>
<FlatList
data={customerData}
keyExtractor={(customerData) => customerData.code.toString()}
//I need to access code and name in the parent component
renderItem={({ item: { code, name, email, balance } }) => {
return (
<View>
<CustomerView
code={code}
name={name}
email={email}
balance={balance}
buttonPress={onPressSelectCustomer}
/>
</View>
);
}}
/>
</View>
);
};
And my home component which is supposed to get code, name passed through in order to trigger an event with there data being passed through
const SelectCustomerScreen = ({navigation}) => {
const customers = useSelector((state: RootStateOrAny) => state.customers);
const getCustomerDetails = (code, name) => {
//navigation.navigate("orderScreen");
console.log(code, name)
}
return (
<View style={{ flex: 1 }}>
<CustomerFlatList
customerData={customers}
doc_type={documentType}
invoiceScreen={invoiceScreen}
quotationScreen={quotationScreen}
onPressSelectCustomer={getCustomerDetails}
/>
</View>
);
};
I am using FlatList to render items. Each item is a separate card style component. Each item has onPress event handler which changes the component.
Here is my Flatlist.
<FlatList
data={data}
renderItem={({ item }) => {
return <CardItem courseData={item} />
}}
ref={this.flatList}
keyExtractor={
(item) => { return item.content_address }
}
initialNumToRender={10}
showsVerticalScrollIndicator={false}
style={{ marginTop: 50 }}
/>
Here is the CardItem Component
constructor{
this.state = {change:false}
}
_onPress = () => {
this.setState({change: true})
}
render() {
if (this.state.change) {
return (//return changes)
} else {
return (
<TouchableOpacity
ref="myRef"
activeOpacity={0.5}
onPress={this._onPress}>
...
</TouchableOpacity>
)
}
}
Now what I want is to have only one card component changed at a time.
So when a user touches on 1st card component, it should change. But when a user touches 2nd card component, 1st should change back to the previous state and 2nd should change.
I saw FlatList documentation here but not sure which methods can help me?
If you store your toggled item in parent state you can check and render accordingly. Also storing toggled value in child state will cause a bug where if the item moves enough off to the screen it will be unmounted and the internal state of the child component will be reset. This would cause undesired toggle in your list. Storing state in parent component will help to overcome this issue.
Example
class App extends Component {
constructor() {
this.state = { toggledItem: null }
}
onPressItem = (itemId) => {
this.setState({toggledItem: itemId})
}
render() {
<FlatList
data={data}
renderItem={({ item }) => {
return <CardItem
courseData={item}
onPress={this.onPressItem}
toggeled={item.id === this.state.toggledItem}
/>
}}
ref={this.flatList}
keyExtractor={
(item) => { return item.content_address }
}
initialNumToRender={10}
showsVerticalScrollIndicator={false}
style={{ marginTop: 50 }}
/>
}
}
class CardItem extends Component {
_onPress = () => {
this.props.onPress(this.props.courseData.id)
}
render() {
if (this.props.toggeled) {
return (//return changes)
} else {
return (
<TouchableOpacity
ref="myRef"
activeOpacity={0.5}
onPress={this._onPress}>
...
</TouchableOpacity>
)
}
}
}