The way I get the data is inside the componentWillMount method, its working fine when it renders the FlatList when the app boots up, but when I'm adding data, all I want is to update also my flatList. How can I do that here's the code snippet:
componentWillMount() {
console.log(`componentDidUpdate`, prevProps);
const user = firebase.auth().currentUser;
if (user != null) {
const db = firebase.firestore();
const docRef = db.collection('userCheckInHistory').doc(user.uid);
docRef
.get()
.then(doc => {
if (doc.exists) {
let shopId = this.props.shopId;
let userShopHistoryData = doc.data();
let userShopPick = userShopHistoryData[shopId];
this.setState({
checkInHistory: userShopPick,
shopId: this.props.shopId
});
} else {
console.log('No such document!');
return false;
}
})
.catch(function(error) {
console.log('Error getting document:', error);
});
}
}
Now this is my FlatList code:
renderHistoryList() {
const sortedData = Common.getArrayByObject(this.state.checkInHistory);
if (this.state.checkInHistory !== '') {
return (
<FlatList
data={sortedData}
extraData={this.state}
keyExtractor={this._keyExtractor}
renderItem={({ item }) => (
<MyListItem
id={item}
onPressItem={this._onPressItem}
selected={!!this.state.selected.get(item)}
title={item}
/>
)}
/>
);
} else {
return <Spinner />;
}
}
When you get your data from api set your state with that data. And then if you add data and setState, react native will rerender app and you will see your data on the list.
Related
I have a Firestore collection, schemed as follows:
posts{
uid{
userPosts{
postID{
creation:
postText:
}
}
}
}
I want to display all of the posts, so I've made the corresponding queries and saved them in posts - an array of all the posts that I later iterate through.
The problem with the way I do it is that it keeps adding the same posts every render. So I've tried to set the array each time, but that way the code never passes through these posts && posts.length > 0 condition.
I'm really new to RN and JS in general, but what I was expecting is
Nothing to show here
at first, and then the list of posts.
The complete component:
import { Text, Pressable, FlatList, SafeAreaView } from "react-native";
import { globalStyles } from "../../styles/global";
import React, { useState, useEffect } from "react";
import { db } from "../../../firebase";
import Post from "../../API/Post";
import { collection, getDocs } from "firebase/firestore";
const FeedScreen = ({ navigation }) => {
const [posts, setPosts] = useState([]);
useEffect(() => {
const getPostData = async () => {
setPosts([]); // ---> Without this line the posts keeps adding each render
const q = collection(db, "posts");
const docSnap = await getDocs(q);
docSnap.docs.map(async (item) => {
const tmp = collection(db, "posts", item.id, "userPosts");
const tmpSnap = await getDocs(tmp);
tmpSnap.docs.map(async (element) => {
setPosts((prev) => {
prev.push(element.data());
return prev;
});
});
});
};
getPostData().catch(console.error);
return;
}, []);
return (
<SafeAreaView style={globalStyles.global}>
{posts && posts.length > 0 ? (
<FlatList
data={posts}
renderItem={({ item }) => (
<Post
post={item}
navigation={navigation}
style={globalStyles.list_of_posts}
/>
)}
keyExtractor={(item, index) => index.toString()}
/>
) : (
<Text>Nothing to show here</Text>
)}
<Pressable
title="edit"
onPress={() => {
navigation.navigate("CreatePost", { navigation });
}}
style={globalStyles.plus_btn}
>
<Text style={globalStyles.plus_btn_text}>+</Text>
</Pressable>
</SafeAreaView>
);
};
export default FeedScreen;
As said, I'm new to this so I'd love an explanation of what actually happens and how to do it properly.
I think the prev value of setPosts will always be [] since it does not immediately update if you call it. A standard way to do it is to call setPosts at the end of your function. Can you try this one?
useEffect(() => {
const getPostData = async () => {
const q = collection(db, "posts");
const docSnap = await getDocs(q);
const promises = docSnap.docs.map(async (item) => {
const tmp = collection(db, "posts", item.id, "userPosts");
const tmpSnap = await getDocs(tmp);
return tmpSnap.docs.map((element) => element.data());
});
const arrayOfPosts = await Promise.all(promises);
let newPosts = [];
arrayOfPosts.forEach((posts) => {
newPosts = [...newPosts, ...posts];
});
setPosts(newPosts);
};
getPostData().catch(console.error);
return;
}, []);
This is my code:
export default function App() {
const [onProcess, setOnProcess] = useState("normal")
var myid = "123"
const [data, setData] = useState([]);
useEffect(() => {
fetch('https://api.npoint.io/0294bea2185268c9ac70')
.then((response) => response.json())
.then((json) => setData(json))
.catch((error) => console.log('ERR :', error))
},[]);
for (let x in data) {
if (data[x].client_id == myid) {
var set = data[x].situation
setOnProcess(set)
console.log(data[x].situation)
break
}
}
const rt_normal = (
<View style={styles.container}>
<Text> This is normal view </Text>
</View>
)
const rt_process = (
<View style={styles.container}>
<Text> This is process view </Text>
</View>
)
if (onProcess == "normal") {
return rt_normal
}
else if (onProcess == "_on_process") {
return rt_process
}
}
The error I got is:
:[Render Error. Too many re-renders. React limits the number of renders to prevent an infinite loop.]
This happens because of setOnProcess(set) code. How can I solve this?
You should remove your for...in loop and refactor to utilise useEffect.
useEffect(() => {
// Get a specific entry where client_id matches myId.
const filteredItem = data.find(item => item.client_id === myId);
// Perform a check as .find() can return undefined.
if(filteredItem.situation) {
setSituation(filteredItem.situation);
}
}, [data]);
Put the for loop inside a useEffect
(Untested) example:
useEffect(() => {
for (let x in data) {
if (data[x].client_id == myid) {
var set = data[x].situation;
setOnProcess(set);
console.log(data[x].situation);
break;
}
}
}, [data]);
I am generating a random key name for AsyncStorage each time user saves an item. These are then displayed in FlatList (using SwipeListView library for swipe to delete button). Now if I call await AsyncStorage.removeItem(key); when the user taps "Delete", I presume the item will just disappear from the list. What I'm completely lost on is how I am supposed to get my random key name? Struggling to find much on FlatList and AsyncStorage, not sure what good practice is.
FlatList:
export default class RecentMealsScreen extends Component {
constructor() {
super();
this.state={
meals: []
}
}
componentDidMount() {
this.getAllMeals();
}
getAllMeals = async () => {
try {
const data = [];
let keys = await AsyncStorage.getAllKeys();
for (let inKey of keys) {
let obj = await AsyncStorage.getItem(inKey);
obj = JSON.parse(obj);
data.push(obj);
}
this.setState({
meals: data
})
} catch (error) {
console.log("Error saving all meals. Error: " + error)
}
}
renderHiddenItem = () => (
<View style={styles.rowBack}>
<View style={[styles.backRightBtn, styles.backRightBtnRight]}>
<Text style={styles.backTextWhite}>Delete</Text>
</View>
</View>
);
deleteMeal = async (key) => {
try {
await AsyncStorage.removeItem(key);
} catch (error) {
console.log('Error deleting Meal: ' + error)
}
}
// Get Meal IDs and display them in list
render() {
return (
<View style={styles.container}>
<SwipeListView
data={this.state.meals}
renderItem={ ({item}) =>
<View style={styles.container}>
<Meal
image = {item.image}
order={item.orderName}
company={item.companyName}
price={item.price}
dateTime={item.dateTime}
notes={item.notes}
rating = {item.rating}
/>
</View>
}
disableRightSwipe
renderHiddenItem={this.renderHiddenItem}
rightOpenValue={-Dimensions.get('window').width}
useNativeDriver={false}
onSwipeValueChange={this.deleteMeal()}
/>
</View>
);
}
}
Save Logic:
saveMeal = async () => {
try {
let meal = {
image: this.state.imageSource,
orderName: this.state.orderText,
companyName: this.state.selectedCompany,
price: this.state.priceText,
dateTime: this.state.dateTimeText,
notes: this.state.notesTextField,
rating: this.state.starCount
};
const ID = await Random.getRandomBytesAsync(16);
await AsyncStorage.setItem(ID.toString(), JSON.stringify(meal)).then(() => {
// Redirect to new screen
Actions.recentMeals();
})
} catch (error) {
console.log("Save Meal error: " + error)
}
}
Sometghing really basic but I didn't understant.
Once I get the contacts how can I use them to populate the Flatlist?
I always get Can't find variable: contacts
import * as Contacts from "expo-contacts";
const ContactsScreen = props => {
useEffect(() => {
(async () => {
const { status } = await Contacts.requestPermissionsAsync();
if (status === "granted") {
const { data } = await Contacts.getContactsAsync({
fields: [Contacts.Fields.Emails]
});
if (data.length > 0) {
const contact = data[0];
console.log(contact);
}
}
})();
}, []);
return (
<View >
<Text>Contacts Module</Text>
<FlatList
data={contact}
keyExtractor={contact.id}
renderItem={({ item }) => (
<ContactItem
firstName={item.firstName}
/>
</View>
);
};
export default ContactsScreen;
I think it's really simple, I just don't understand
You need to keep your contacts in the component's state. So every time you change your state, your component will render itself and you will see the updated data.
Change your code with the following. Don't forget to import useState.
import * as Contacts from "expo-contacts";
const ContactsScreen = props => {
const [myContacts, setMyContacts] = useState([]);
useEffect(() => {
(async () => {
const { status } = await Contacts.requestPermissionsAsync();
if (status === "granted") {
const { data } = await Contacts.getContactsAsync({
fields: [Contacts.Fields.Emails]
});
if (data.length > 0) {
setMyContacts(data);
}
}
})();
}, []);
return (
<View >
<Text>Contacts Module</Text>
<FlatList
data={myContacts}
keyExtractor={item => item.id}
renderItem={({ item }) => (
<Text>{item.firstName}</Text>
)}
/>
</View>
);
};
export default ContactsScreen;
Answer from my comment:
I think that might be because of the scope of the variable , it could be that RN doenst know it exists because it only lives inside the function. I guess you could set up a State and then assign the values from contact to the state and in ur flatlist call data ={ this.state.contact}.
or by using hooks like you do :
if (data.length > 0) {
setContact(data);
}
and call it in flatlist:
data={myContact} // if named so in state declaration
I implement a very simple list that calls a server that returns a page containing books.Each book has a title, author, id, numberOfPages, and price). I use a Flat List in order to have infinite scrolling and it does its job very well two times in a row (it loads the first three pages) but later it doesn't trigger the handler anymore.
Initially it worked very well by fetching all available pages, but it stopped working properly after I added that extra check in local storage. If a page is available in local storage and it has been there no longer than 5 seconds I don't fetch the data from the server, instead I use the page that is cached. Of course, if there is no available page or it is too old I fetch it from the server and after I save it in local storage.(Something went wrong after adding this behavior related to local storage.)
Here is my component:
export class BooksList extends Component {
constructor(props) {
super(props);
this.state = {
pageNumber: 0
};
}
async storePage(page, currentTime) {
try {
page.currentTime = currentTime;
await AsyncStorage.setItem(`page${page.page}`, JSON.stringify(page));
} catch (error) {
console.log(error);
}
}
subscribeToStore = () => {
const { store } = this.props;
this.unsubsribe = store.subscribe(() => {
try {
const { isLoading, page, issue } = store.getState().books;
if (!issue && !isLoading && page) {
this.setState({
isLoading,
books: (this.state.books ?
this.state.books.concat(page.content) :
page.content),
issue
}, () => this.storePage(page, new Date()));
}
} catch (error) {
console.log(error);
}
});
}
componentDidMount() {
this.subscribeToStore();
// this.getBooks();
this.loadNextPage();
}
componentWillUnmount() {
this.unsubsribe();
}
loadNextPage = () => {
this.setState({ pageNumber: this.state.pageNumber + 1 },
async () => {
let localPage = await AsyncStorage.getItem(`page${this.state.pageNumber}`);
let pageParsed = JSON.parse(localPage);
if (localPage && (new Date().getTime() - localPage.currentTime) < 5000) {
this.setState({
books: (
this.state.books ?
this.state.books.concat(pageParsed.content) :
page.content),
isLoading: false,
issue: null
});
} else {
const { token, store } = this.props;
store.dispatch(fetchBooks(token, this.state.pageNumber));
}
});
}
render() {
const { isLoading, issue, books } = this.state;
return (
<View style={{ flex: 1 }}>
<ActivityIndicator animating={isLoading} size='large' />
{issue && <Text>issue</Text>}
{books && <FlatList
data={books}
keyExtractor={book => book.id.toString()}
renderItem={this.renderItem}
renderItem={({ item }) => (
<BookView key={item.id} title={item.title} author={item.author}
pagesNumber={item.pagesNumber} />
)}
onEndReachedThreshold={0}
onEndReached={this.loadNextPage}
/>}
</View>
)
}
}
In the beginning the pageNumber available in the state of the component is 0, so the first time when I load the first page from the server it will be incremented before the rest call.
And here is the action fetchBooks(token, pageNumber):
export const fetchBooks = (token, pageNumber) => dispatch => {
dispatch({ type: LOAD_STARTED });
fetch(`${httpApiUrl}/books?pageNumber=${pageNumber}`, {
headers: {
'Authorization': token
}
})
.then(page => page.json())
.then(pageJson => dispatch({ type: LOAD_SUCCEDED, payload: pageJson }))
.catch(issue => dispatch({ type: LOAD_FAILED, issue }));
}
Thank you!