React native VirtualizedList Re-render while scroll the list - reactjs

I have Virtualized List initial render record up to 30 ,while render the data list automatically re render 2 to 4 times and also the new data added to the list
while rendering multi times we can't able to do any action like touch or navigate to another screen
My Code
class HomeDetails extends PureComponent {
constructor(props) {
super(props);
this.cellRefs = {};
this.flatListRef = React.createRef();
}
getItem = (data, index) => {
if (index in data) {
return {
key: `${data[index].id} - ${index}`,
id: data[index].id,
accountId: data[index].accountId,
displayName: data[index].displayName,
fullName: data[index].fullName,
};
}
};
keyExtractor(item, index) {
return `${item.id} - ${index}`;
}
getItemCount = data => {
return data.length;
};
_renderItem =({item,index}) => {
console.log(
'Rerendring',
item.accountId,
moment().format('MM/DD/YY hh:mm:ss a'),
);
return (
<View key={index} style={{height: 50, flexDirection: 'row'}}>
<Text>{`${item.accountId} ${moment().format(
'MM/DD/YY hh:mm:ss a',
)}`}</Text>
</View>
);
}
render(){
return (
<VirtualizedList
onScroll={this.onScrollHandler}
onViewableItemsChanged={this._onViewableItemsChanged}
viewabilityConfig={viewabilityConfig}
scrollEventThrottle={16}
ref={this.flatListRef}
horizontal={false}
decelerationRate="normal"
showsHorizontalScrollIndicator={false}
showsVerticalScrollIndicator={false}
data={this.props.responseRecord}
pagingEnabled={true}
scrollToOverflowEnabled={false}
renderItem={this._renderItem}
keyExtractor={this.keyExtractor}
getItemCount={this.getItemCount}
getItem={this.getItem}
windowSize={21}
progressViewOffset={20}
initialNumToRender={15}
maxToRenderPerBatch={15}
updateCellsBatchingPeriod={100}
onEndReached={val => {
return this.props.getExtraData(2, 1);
}}
onEndReachedThreshold={0.1}
refreshing={this.props.postLoading}
extraData={this.props.refreshData}
disableIntervalMomentum={false}
removeClippedSubviews={true}
onRefresh={() => {
return this.props.getExtraData(1, 1);
}}
ItemSeparator={this.ItemSeparator}
ListFooterComponent={this.renderFooter}
/>
)
}
}
const mapStateToProps = ({post, auth, common}) => {
const {
responseRecord,
postLoading,
refreshData,
} = post;
return {
responseRecord,
postLoading,
refreshData,
};
};
const mapDispatchToProps = {
getExtraData,
};
export default connect(mapStateToProps, mapDispatchToProps)(HomeDetails);
..........................................................................
1.For initial 30 record rendering its re-render more that 2 times
2.when add more records its re-render more than 4 to 6 times
3.tried with purecomponent but no luck
code deployed in snack
https://snack.expo.dev/#pandianvpsm/cd5737

Internal, React's PureComponent implements the shouldComponentUpdate method and compares previous props values and new props or state values to avoid unnecessary re-renders.
This works well for primitive type values like numbers, strings, and booleans.
For referential types values (objects and arrays), this comparison is not always accurate. This is the behavior you have. this.props.responseRecord is an array of objects (referential types).
We can solve this problem by implementing our own componentShouldUpdate method as below:
/** Trigger component rerender only new elements added */
shouldComponentUpdate(nextProps, nextState) {
return this.props.responseRecord.length !== nextProps.responseRecord.length
}
Full code below
class HomeDetails extends React.Component {
constructor(props) {
super(props);
this.cellRefs = {};
this.flatListRef = React.createRef();
}
/** Trigger component rerender only new elements added */
shouldComponentUpdate(nextProps, nextState) {
return this.props.responseRecord.length !== nextProps.responseRecord;
}
getItem = (data, index) => {
if (index in data) {
return {
key: `${data[index].id} - ${index}`,
id: data[index].id,
accountId: data[index].accountId,
displayName: data[index].displayName,
fullName: data[index].fullName,
};
}
};
keyExtractor(item, index) {
return `${item.id} - ${index}`;
}
getItemCount = (data) => {
return data.length;
};
_renderItem = ({ item, index }) => {
console.log(
"Rerendring",
item.accountId,
moment().format("MM/DD/YY hh:mm:ss a")
);
return (
<View key={index} style={{ height: 50, flexDirection: "row" }}>
<Text>{`${item.accountId} ${moment().format(
"MM/DD/YY hh:mm:ss a"
)}`}</Text>
</View>
);
};
render() {
return (
<VirtualizedList
onScroll={this.onScrollHandler}
onViewableItemsChanged={this._onViewableItemsChanged}
viewabilityConfig={viewabilityConfig}
scrollEventThrottle={16}
ref={this.flatListRef}
horizontal={false}
decelerationRate="normal"
showsHorizontalScrollIndicator={false}
showsVerticalScrollIndicator={false}
data={this.props.responseRecord}
pagingEnabled={true}
scrollToOverflowEnabled={false}
renderItem={this._renderItem}
keyExtractor={this.keyExtractor}
getItemCount={this.getItemCount}
getItem={this.getItem}
windowSize={21}
progressViewOffset={20}
initialNumToRender={15}
maxToRenderPerBatch={15}
updateCellsBatchingPeriod={100}
onEndReached={(val) => {
return this.props.getExtraData(2, 1);
}}
onEndReachedThreshold={0.1}
refreshing={this.props.postLoading}
extraData={this.props.refreshData}
disableIntervalMomentum={false}
removeClippedSubviews={true}
onRefresh={() => {
return this.props.getExtraData(1, 1);
}}
ItemSeparator={this.ItemSeparator}
ListFooterComponent={this.renderFooter}
/>
);
}
}
const mapStateToProps = ({ post, auth, common }) => {
const { responseRecord, postLoading, refreshData } = post;
return {
responseRecord,
postLoading,
refreshData,
};
};
const mapDispatchToProps = {
getExtraData,
};
export default connect(mapStateToProps, mapDispatchToProps)(HomeDetails);

Related

get element in react native

I want to create a Text element inside a View element, how do I link that? I've tried the following. (After typing inside the input, a search is made in the database and the result is translated into a text element).
class SearchScreen extends React.Component {
state = {
inputValue: "",
};
search() {
//Here I do the search in Firebase Realtime Database (it works)
var textElement = React.createElement(
Text,
{ style: { fontSize: 20 } },
[...] //Here inside is the retrieved data from the database
);
var resultView = useRef(resultView); //This doesn't work
ReactDOM.render(textElement, resultView);
}
setSearch = (inputValue) => {
this.setState({ inputValue }, () => this.search());
};
render() {
return (
<View>
<TextInput
onChangeText={(inputValue) => this.setSearch(inputValue)}
value={this.state.inputValue}
/>
<View ref="resultView">
</View>
</View>
)
}
}
export default SearchScreen;
ReactDom doesn't work with React Native.
Try something like this:
class SearchScreen extends React.Component {
state = {
inputValue: '',
resultText: '',
resultList: [],
};
search() {
//Here I do the search in Firebase Realtime Database (it works)
//[...] //Here inside is the retrieved data from the database
// Simulate request to the database
setTimeout(() => {
const databaseResultText = 'Hello World';
this.setState({
resultText: databaseResultText,
});
const databaseResultList = [
{
name: 'Bob',
},
{
name: 'Steve',
},
];
this.setState({
resultList: databaseResultList,
});
}, 1000);
}
setSearch = (inputValue) => {
this.setState({inputValue}, () => this.search());
};
render() {
return (
<View>
<TextInput
onChangeText={(inputValue) => this.setSearch(inputValue)}
value={this.state.inputValue}
/>
<View>
<Text>{this.state.resultText}</Text>
</View>
<View>
{this.state.resultList.map((item) => {
return <Text>{item.name}</Text>;
})}
</View>
</View>
);
}
}
export default SearchScreen;

How to execute a function when some item renders in react native?

I have a sectionlist of Contacts where I am displaying both device and online contacts of a user. The online contacts api doesnt give me all the contacts at once. So I have to implement some pagination. I am also fetching all device contacts and first page of online contacts and sorting them to show in sectionlist, but the problem is, to load more contacts, i have to keep track of the last item rendered in my state and in the render function I am calling pagination function to load more contacts. and then i am updating the state of fetched online contact. But its an unsafe operation, is there a better way to achieve this?
I want to execute a function when the specific item renders and it can update the state.
Here is some code: ContactList.tsx
import React, { Component } from "react";
import {
View,
StyleSheet,
SectionListData,
SectionList,
Text
} from "react-native";
import { Contact } from "../../models/contact";
import ContactItem from "./contact-item";
export interface ContactsProps {
onlineContacts: Contact[];
deviceContacts: Contact[];
fetchMoreITPContacts: () => void;
}
export interface ContactsState {
loading: boolean;
error: Error | null;
data: SectionListData<Contact>[];
lastItem: Contact;
selectedItems: [];
selectableList: boolean;
}
class ContactList extends Component<ContactsProps, ContactsState> {
private sectionNames = [];
constructor(props: ContactsProps, state: ContactsState) {
super(props, state);
this.state = {
loading: false,
error: null,
data: [],
lastItem: this.props.onlineContacts[this.props.onlineContacts.length - 1]
};
for (var i = 65; i < 91; ++i) {
this.sectionNames.push({
title: String.fromCharCode(i),
data: []
});
}
}
private buildSectionData = contacts => {
this.sort(contacts);
const data = [];
const contactData = this.sectionNames;
contacts.map(contact => {
const index = contact.name.charAt(0).toUpperCase();
if (!data[index]) {
data[index] = [];
contactData.push({
title: index,
data: []
})
}
data[index].push(contact);
});
for (const index in data) {
const idx = contactData.findIndex(x => x.title === index);
contactData[idx].data.push(...data[index]);
}
this.setState({
loading: false,
error: null,
lastItem: contacts[contacts.length - 1],
data: [...contactData]
});
};
private sort(contacts) {
contacts.sort((a, b) => {
if (a.name > b.name) {
return 1;
}
if (b.name > a.name) {
return -1;
}
return 0;
});
}
componentDidMount() {
const contacts = [].concat(
this.props.deviceContacts,
this.props.onlineContacts
);
this.buildSectionData(contacts);
}
componentDidUpdate(
prevProps: Readonly<ContactsProps>,
prevState: Readonly<ContactsState>,
snapshot?: any
): void {
if (this.props.onlineContacts !== prevProps.onlineContacts) {
const from = this.props.itpContacts.slice(
prevProps.onlineContacts.length,
this.props.onlineContacts.length
);
this.buildSectionData(from);
}
}
renderItem(item: any) {
if (!!this.state.lastItem && !this.state.loading)
if (item.item.id === this.state.lastItem.id) {
this.setState({
loading: true
});
this.props.fetchMoreOnlineContacts();
}
return <ContactItem item={item.item} />;
}
render() {
return (
<View style={styles.container}>
<SectionList
sections={this.state.data}
keyExtractor={(item, index) => item.id}
renderItem={this.renderItem.bind(this)}
renderSectionHeader={({ section }) =>
section.data.length > 0 ? (
<Text style={styles.sectionTitle}>
{section.title}
</Text>
) : null
}
/>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1
},
sectionTitle: {
paddingBottom: 30,
paddingLeft: 25,
fontWeight: "bold",
fontSize: 20
}
});
export default ContactList;
Yeah after some thoughts I got the answer may be.
instead of calling fetchMoreContacts from renderItem I passed the lastItem as a prop to the ContactItem component.
and in the constructor I checked If the item is lastItem and called to fetchMoreContact.
and it worked!

FlatList renderItem not being highlighted when clicked the first time

Basically, I'm trying to setup a Flatlist in which multiple values can be selected.
My problem is with the styling of elements, when clicked the first time they don't get highlighted but when clicked the 2nd time they get highlighted.
FlatList Code
renderRow = ({item}) => (
<RowItem data={item} />
)
data = [
{
value: 'element1'
},
{
value: 'element2'
}
]
render(){
return (
<FlatList
data={this.data}
renderItem={this.renderRow}
keyExtractor={(item, index) => item + index}/>
)
}
RowItem Code
export default class RowItem extends React.Component {
state = {
isElementActive: false,
}
highlightElement = () => {
this.setState(prevState => ({
isElementActive: !prevState.isElementActive
}))
}
render() {
return (
<TouchableOpacity
activeOpacity={0.7}
onPress={this.highlightElement}
style={[styles.container, this.state.isElementActive ? styles.activeContainer : styles.inactiveContainer]}>
<Text>{this.props.data.value}</Text>
</TouchableOpacity>
)
}
}
const styles = Stylesheet.create({
container: {
height: 100,
width: 300,
backgroundColor: 'red',
},
activeContainer: {
opacity: 0.7,
},
inactiveContainer: {
opacity: 1,
}
});
When clicking on the element once, the value of the isElementActive returns true (when I console.log it) but the styling "activeContainer" does not apply. However, when I click it again, the styling applies even though the value of isElementActive then becomes false.
By default the value starts off as false, and they are not highlighted (i.e. have opacity of 1) and for this reason I'm kind of confused when clicked the first time, the value of isElementActive changes but the styling does not apply.
I was able to make it work with setOpacityTo after the setState.
Working example: https://snack.expo.io/SJNSKQPIB
import React from 'react';
import {TouchableOpacity, FlatList, StyleSheet, Text} from 'react-native';
type State = {
active: boolean;
};
type Props = {
value: string;
};
class RowItem extends React.Component<Props, State> {
state = {
active: null,
};
ref = null;
highlightElement = () => {
this.setState(
prevState => ({
active: !prevState.active,
}),
() => {
this.ref.setOpacityTo(this.state.active ? 0.7 : 1);
},
);
};
render() {
return (
<TouchableOpacity
ref={ref => (this.ref = ref)}
onPress={this.highlightElement}
style={[styles.container]}>
<Text>{this.props.value}</Text>
</TouchableOpacity>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
height: 100,
backgroundColor: 'red',
},
});
export default class App extends React.Component {
data = [
{
value: 'element1',
},
{
value: 'element2',
},
];
render() {
return (
<FlatList
keyExtractor={(_, index) => index.toString()}
data={this.data}
renderItem={({item}) => <RowItem value={item.value} />}
/>
);
}
}

How to change each of state of list item separately in react redux?

Using
react-redux
redux-persist
redux-actions
react-native
I'm learning react-redux and react-native by myself.
I'm trying to update a single item from list in react-redux now.
There's many categories and stores in my project.
An user would select one of categories, and then click 'like button' of one item in store list.
It's like instagram or facebook.
When I change one item's state, the state of every item in the store list change at the same time.
I have no idea why it happens.
I set the structure to ducks pattern to avoid change too much files when to change state.
If anyone give some advice, I would appreciate and it could be helpful for me. Thank you.
I saw some article to resolve this issue, have to give id to distinguish items and make the payload as object. I didn't understand well, so my code is messy now. But I'd like to know what happen to my code, so I share my code.
restaurantContainer.js
class RestaurantListContainer extends Component {
shouldComponentUpdate(nextProps) {
return nextProps.imgUrls !== this.props.imgUrls;
}
componentDidMount() {
const {category, StoreActions} = this.props;
try {
StoreActions.fetchStoreInfo(category);
StoreActions.fetchImgUrl(category);
this.getUid();
} catch (e) {
console.log(e);
}
}
async getUid() {
const {StoreActions} = this.props;
const uid = await storage.getItem('uid');
StoreActions.fetchLikeList(uid);
}
render() {
const {fetching, tabColor, tabName, category, categoryId, navigation, stores, imgUrls, like} = this.props;
const {onClick} = this;
return (
<View>
...
<ScrollView>
{
fetching ?
<View>
<Bars size={30} color="#40D59B"/>
</View>
:
stores.map((store, i) =>
<View key={`item-${i}`}>
<RestaurantItem
key={`restaurantItem-${i}`}
i={i}
category={category}
navigation={navigation}
storeInfo={store}
imgUrls={imgUrls[i]}
categoryId={categoryId}
like={like}
/>
</View>
)
}
</ScrollView>
</View>
);
}
}
export default connect(
({styleMod, storeMod}) => ({
stores: storeMod.stores,
imgUrls: storeMod.imgUrls,
fetching: storeMod.fetching,
likeList: storeMod.likeList
}),
(dispatch) => ({
StoreActions: bindActionCreators(storeActions, dispatch),
})
)(RestaurantListContainer);
restaurantItem.js
class RestaurantItem extends Component {
pressFunc = (item) => {
const {navigation} = this.props;
const {push} = navigation;
console.log(item.name);
push('RestaurantDetail', {info: item});
}
voteAdder = async (storeName) => {
const uid = await storage.getItem('uid');
const {i, categoryId} = this.props;
if (uid) {
const {VoteActions, LikeActions, category, favoriteStores} = this.props;
try {
VoteActions.voteAdd(favoriteStores, category, storeName, uid);
LikeActions.likeClicked(storeName, category, categoryId, i);
} catch (e) {
console.log(e);
}
} else {
alert('You are not authorized!');
}
}
render() {
const {i, storeInfo, category, categoryId, imgUrls, favoriteStores, like} = this.props;
return (
<View style={restaurantCard}>
<StoreImg
img={imgUrls}
name={storeInfo.name}
/>
<StoreInfoBlock
i={i}
storeInfo={storeInfo}
pressFunc={this.pressFunc}
/>
<View style={{flexDirection: 'column'}} >
{
<ThumbImg
voteAdder={() => this.voteAdder(storeInfo.name)}
name={storeInfo.name}
favoriteStore={favoriteStores[category]}
category={category}
like={like}
categoryId={categoryId}
/>
}
<Score count={storeInfo.count}/>
</View>
</View>
);
}
}
export default connect(
({voterMod, likeMod}) => ({
favoriteStores: voterMod.favoriteStores,
like: likeMod.like,
}),
(dispatch) => ({
VoteActions: bindActionCreators(voteActions, dispatch),
LikeActions: bindActionCreators(likeActions, dispatch),
})
)(RestaurantItem);
thumbImg.js
export default class ThumbImg extends Component {
onClick = () => {
this.props.voteAdder();
}
onFlag = () => {
const {like, categoryId, i} = this.props;
if(like.categoryById[categoryId]) {
if(like.storeById[i]) {
console.log(2);
return (
<FastImage
resizeMode={FastImage.resizeMode.cover}
style={{width: 50, height: 50}}
source={require('...')}
/>
);
} else {
return (
<FastImage
resizeMode={FastImage.resizeMode.cover}
style={{width: 50, height: 50}}
source={require('...')}
/>
);
}
} else {
return (
<FastImage
resizeMode={FastImage.resizeMode.cover}
style={{width: 50, height: 50}}
source={require('...')}
/>
);
}
}
render() {
return (
<TouchableOpacity onPress={this.onClick}>
<View style={{paddingTop: 15, paddingRight: 15}}>
{
this.onFlag()
}
</View>
</TouchableOpacity>
);
}
}
likeMod.js
// Action types
const ON_LIKE = 'like/ON_LIKE';
const OFF_LIKE = 'like/OFF_LIKE';
// action creator
export const likeClicked = (store, category, categoryId, i) => (dispatch) => {
const selectedCategory = {categoryById: {}};
const categoryInfo = {
id: categoryId,
name: category,
};
selectedCategory.categoryById[categoryId] = categoryInfo;
const selectedStore = {storeById: {}};
const storeInfo = {
id: i,
name: store
}
selectedStore.storeById[i] = storeInfo;
const favorites = {
...selectedCategory,
...selectedStore
}
dispatch({type: ON_LIKE, payload: favorites});
}
const initialState = {
like: {
categoryById: {},
storeById: {}
}
};
// Reducer
export default handleActions({
[ON_LIKE]: (state, action) => ({...state, like: action.payload}),
[OFF_LIKE]: (state, action) => ({...state, like: action.payload}),
}, initialState);

Remove one item from flatlist

I'm using FlatList to show a list of data.
I was trying dozens of example how to remove one row from data, but couldn't find the right solution.
Right now I'm removing all data from state, but I want to remove just one item.
Here is my HomeScreen which displays list of data:
class HomeScreen extends React.Component {
constructor(props) {
super(props);
this.state = {
data: data.products
};
}
static navigationOptions = {
title: "Products"
};
keyExtractor = (item, index) => item.id;
openDetails = data => {
this.props.navigation.navigate("Details", {
data
});
};
deleteItem = data => {
this.setState({ data: ''})
}
renderProduct = ({ item, index }) => {
return (
<Item
itemTitle={item.title}
openDetails={() => this.openDetails(item)}
itemUrl={item.imageUrl}
data={this.state.data}
deleteItem={() => this.deleteItem(item)}
/>
);
};
render() {
return (
<FlatList
data={this.state.data}
renderItem={this.renderProduct}
keyExtractor={this.keyExtractor}
/>
);
}
}
export default HomeScreen;
Here is my Item component which is showing one item and receiving deleteRow function as prop:
const Item = props => {
return (
<View>
<TouchableOpacity onPress={props.deleteItem}>
<Image
source={{ uri: props.itemUrl }}
style={{ width: "100%", height: 220 }}
/>
<Text style={styles.productTitle}>{props.itemTitle}</Text>
</TouchableOpacity>
</View>
);
};
export default Item;
Use below deleteItem function.
deleteItem = data => {
let allItems = [...this.state.data];
let filteredItems = allItems.filter(item => item.id != data.id);
this.setState({ data: filteredItems })
}
This should filter out the deleted item.

Resources