Duplicate asynchronous component twice render the last - reactjs

I create the same component (Recipes) or Screen twice, sending different properties.
<Recipes setRecipes = {this.setRecipes} id="1" />
<Quantity setQuantity = {this.setQuantity} />
<Recipes setRecipes = {this.setRecipes2} id="2" />
<Quantity setQuantity = {this.setQuantity} />
The properties-functions are the following. Only states change.
setRecipes = (recipe) => {
this.setState({recipe:recipe})
}
setRecipes2 = (recipe2) => {
this.setState({recipe2:recipe2})
}
In mty component called Recipes, I get my local database (I use pouchdb), my recipes and products asicronically.
import React, { Component } from 'react';
import { Text, View, TouchableOpacity, ScrollView } from 'react-native';
import style from './Styles/styleNew';
import PouchDB from 'pouchdb-react-native';
PouchDB.plugin(require('pouchdb-find'));
const gThis = null;
const db = new PouchDB('X')
export default class Recipes extends Component {
constructor(props) {
super(props);
gThis = this;
this.state = {
setRecipes: this.props.setRecipes,
id: this.props.id,
recipeID: null,
options: [ ],
};
}
getRecipes() {
let data = []
db.find({
selector: {
type: {
"$eq": this.state.id,
},
owner: {
"$in": ['user']
},
},
}).then(function (recipes) {
recipes = recipes.docs
for (let i = 0; i < recipes.length; i++) {
recipe = recipes[i]
let current = {
id: recipe._id,
name: recipe.name,
recipe: recipe,
}
data.push(current)
}
gThis.setState({ options: data })
}).catch(function (err) {
console.warn(err.toString())
});
}
componentDidMount() {
this.getRecipes()
}
handleBackPress = () => {
this.props.navigation.goBack();
}
defineRecipe(recipe, index) {
this.setState({ recipeID: recipe._id });
this.state.setRecipes(recipe)
}
render() {
return (
<View style={{ flex: 1 }}>
<Text style={style.listText}>RECIPES {this.state.id} </Text>
<ScrollView style={style.listScroll}>
{this.state.options.length >0 &&
this.state.options.map((item, index) => {
key = index + '-' + this.state.id
//key = new Date().toISOString()
console.log(key)
return (
<TouchableOpacity
style={this.state.recipeID === item.id ? style.listOptionSelected : style.listOption}
onPress={() => { this.defineRecipe(item.recipe, index);}}
key={key} >
<Text style={style.listOptionText}>{item.name}</Text>
</TouchableOpacity>
);
})
}
</ScrollView>
</View>
);
}
}
But the second Screen Recipe is rendered twice.
Render 1
Render 2
As you can see, the content is replaced.

I think it might have something to do with you gThis which I assume is a global this?
A better way to try and access this inside your class members is to either use lexical arrow functions in your class like this:
getRecipes = () => {
// implementation
}
Or bind your class members to this in the constructor as follows
constructor(props) {
super(props);
gThis = this;
this.state = {
id: this.props.id,
recipeID: null,
options: [ ],
};
this.getRecipes = this.getRecipes.bind(this);
this.defineRecipe = this.defineRecipe.bind(this);
}
On a different node, you have babel transpiling your ES6 code from as far as I can see.
I would strongly recommend replacing your for loops with es6 variants such as a map or forEach for readability
const options = recipes.map(recipe => {
const { _id: id, name } = recipe;
data.push({
id,
name,
recipe,
});
});
this.setState({ options });

Related

React native VirtualizedList Re-render while scroll the list

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);

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;

React state is not updated immediately after deleting data

I have a problem to update the view in React Native after deleting a POST.
I think it could be a problem with the "state" but don't know how to fix it.
This is my list of Posts.
When I press on an item, it ask us to confirm the action.
When we confirm the action of delete, POST is deleted from Firebase but the view is not updated (Still 2 items in the list but only one in database. if I refresh and re-enter to the component, the list is updated)
This is my code :
class GetPosts extends React.Component {
static navigationOptions = ({navigation}) => {
const {params} = navigation.state;
};
constructor(props) {
super(props);
this.state = {
data: {},
data2: [],
posts: {},
newArray: [],
postsCount: 0,
};
}
componentDidMount() {
var f_id = this.props.identifier;
firebase
.database()
.ref('/posts/')
.orderByKey()
.on('value', snapshot => {
snapshot.forEach(el => {
if (el.val().film_id == f_id) {
this.state.data = [
{
email: el.val().email,
puid: el.val().puid,
username: el.val().username,
time: el.val().time,
text: el.val().text,
},
];
this.setState({
data2: this.state.data2.concat(this.state.data),
});
}
});
this.state.data2.forEach(obj => {
if (!this.state.newArray.some(o => o.puid === obj.puid)) {
this.state.newArray.push({...obj});
}
});
this.setState({
posts: this.state.newArray,
postsCount: _.size(this.state.newArray),
});
console.log('valeur finale POSTS=' + this.state.posts);
});
}
renderPosts() {
const postArray = [];
_.forEach(this.state.posts, (value, index) => {
const time = value.time;
const timeString = moment(time).fromNow();
postArray.push(
<TouchableOpacity
onLongPress={this._handleDelete.bind(this, value.puid)}
key={index}>
<PostDesign
posterName={value.username}
postTime={timeString}
postContent={value.text}
/>
</TouchableOpacity>,
);
//console.log(postArray);
});
_.reverse(postArray);
return postArray;
}
_handleDelete(puid) {
const email = firebase.auth().currentUser.email;
let user_email = firebase.database().ref('/posts');
user_email.once('value').then(snapshot => {
snapshot.forEach(el => {
console.log('Userdb :' + el.val().email);
if (email === el.val().email) {
Alert.alert(
'Supprimer le message',
'Are you sure to delete the post?',
[
{text: 'Oui', onPress: () => this._deleteConfirmed(puid)},
{text: 'Non'},
],
);
//console.log('Userdb :' + el.val().email);
} else {
//
console.log('Usercur :' + email);
}
});
});
}
_deleteConfirmed(puid) {
const uid = firebase.auth().currentUser.uid;
firebase
.database()
.ref('/posts/' + uid + puid)
.remove();
this.setState({
posts: this.state.newArray.filter(user => user.puid !== puid),
});
}
render() {
return (
<View style={styles.container}>
<View style={styles.profileInfoContainer}>
<View style={styles.profileNameContainer}>
<Text style={styles.profileName}>{this.props.email}</Text>
</View>
<View style={styles.profileCountsContainer}>
<Text style={styles.profileCounts}>{this.state.postsCount}</Text>
<Text style={styles.countsName}>POSTS</Text>
</View>
</View>
<ScrollView styles={styles.postContainer}>
{this.renderPosts()}
</ScrollView>
</View>
);
}
}
Thank you in advance !!
Several places in your code you are accessing this.state inside of setState, which can cause problems like this. You should be using a function with prevProps whenever you are accessing state within setState.
For example, within _deleteConfirmed:
this.setState({
posts: this.state.newArray.filter(user => user.puid !== puid),
});
should be changed to:
this.setSate(prevState => ({
posts: prevState.newArray.filter(user => user.puid !== puid),
});

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!

Update Rendered Component Prop Value Async

I creating a couple of lists items with financial data and I want to display a mini graph on the side using react-sparklines.
So I'm trying to fetch the graph data when mapping the array since retrieving the data could take some time but it seems I can't get the graph component to update the prop value correctly.
How can I update the prop data in the Sparklines component once the data has been retreived from fetch?, is it possible?.
Here's a sample of my code
class CurrencyList extends Component {
currencyGraph(symbol) {
return fetch(baseURL, {
method: 'POST',
headers: {
'Authorization': 'JWT ' + token || undefined, // will throw an error if no login
},
body: JSON.stringify({'symbol': symbol})
})
.then(handleApiErrors)
.then(response => response.json())
.then(function(res){
if (res.status === "error") {
var error = new Error(res.message)
error.response = res.message
throw error
}
return res.graph_data
})
.catch((error) => {throw error})
}
render () {
topCurrencies.map((currency,i) => {
return (
<ListItem key={i} button>
<Sparklines data={this.currencyGraph(currency.symbol)} >
<SparklinesLine style={{ stroke: "white", fill: "none" }} />
</Sparklines>
<ListItemText primary={currency.symbol} />
</ListItem>
)
})
}
}
I would wrap this component with my own component and accept the data as props.
In the parent component i will render the list only when i have the data ready and pass each component the relevant data on each iteration.
Here is a running small example
const list = [
{
key: 1,
data: [5, 10, 5, 20]
},
{
key: 2,
data: [15, 20, 5, 50]
},
{
key: 3,
data: [1, 3, 5, 8]
}
];
class MySparklines extends React.Component {
render() {
const { data } = this.props;
return (
<Sparklines data={data} limit={5} height={20}>
<SparklinesLine style={{ fill: "#41c3f9" }} />
</Sparklines>
);
}
}
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
dataList: []
}
}
componentDidMount() {
setTimeout(() => {
this.setState({ dataList: list });
}, 1500);
}
renderCharts = () => {
const { dataList } = this.state;
return dataList.map((d) => {
return(
<MySparklines key={d.key} data={d.data} />
);
})
}
render() {
const { dataList } = this.state;
return (
<div>
{
dataList.length > 0 ?
this.renderCharts() :
<div>Loading...</div>
}
</div>
);
}
}
You can't return data from an asynchronous function like that. Instead, when your load request completes, set the data in the state which will trigger the component to render. Note that in the test case below, it renders a placeholder until the data is ready.
Item = props => <div>{props.name}</div>;
class Test extends React.Component {
state = {
data: ''
}
componentDidMount() {
// Simulate a remote call
setTimeout(() => {
this.setState({
data: 'Hi there'
});
}, 1000);
}
render() {
const { data } = this.state;
return data ? <Item name={this.state.data} /> : <div>Not ready yet</div>;
}
}
ReactDOM.render(
<Test />,
document.getElementById('container')
);
Fiddle

Resources