Doing this:
onPress={() => {
this.setState({
myArray: []
});
const { myArray } = this.state;
console.log(myArray);
}}
Expect the array to be empty, but it still contains old values. If I press the button again, or console.log at a different place in the program, the array is empty. I take it this is due to this.state being asynchronous. This is a problem since, instead of the console.log, I'm planning to have the array saved to AsyncStorelike this:
AsyncStorage.setItem(
"#ShoppingListStore: ShoppingListKey",
JSON.stringify(myArray)
);
If I can't save the array directly after the state has been set, where can I do it? Is there a way to listen for the state to complete setting, then run the AsyncStore code?
Try something like that:
onPress={() => {
this.setState({
...this.state, myArray: []
}, () => console.log(this.state.myArray));
}}
Related
I am using firebase for my app and the data i read i want to put that in state to use it in different places.
it kinda works but when i want to console.log the state it updates like 30 times a second, am i doing something wrong?
this is my code
const db = firebase.firestore();
const [PBS1Detail, setPBS1Detail] = useState();
db.collection('Track').get().then((snapshot) => {
snapshot.docs.forEach(doc => {
renderTracks(doc)
}
)
});
const renderTracks = (doc) => {
let data = doc.data().data[0].Module;
return setPBS1Detail(data);
}
console.log(PBS1Detail)
i already tried to store it in a variable instead of state but thats not working for me, i can't get the variable from the function global
i am a noob i get it xd
You don't need a return statement when setting state. Also, it looks like you're performing some async function which means that when your component renders for the first time, PBS1Detail will be undefined since the db is a pending Promise.
You could/should put your async functions in useEffect hook:
useEffect(()=> {
db.collection('Track').get().then((snapshot) => {
snapshot.docs.forEach(doc => {
renderTracks(doc)
}
)
});
}, [])
const renderTracks = (doc) => {
let data = doc.data().data[0].Module;
setPBS1Detail(data);
}
Finally, your renderTracks function doesn't seem correct as it appears you're looping over docs and resetting your state each time.
Instead, maybe consider having an array for PBS1Detail
const [PBS1Detail, setPBS1Detail] = useState([]);
Then modify your async call:
useEffect(()=> {
db.collection('Track').get().then((snapshot) => {
let results = []
snapshot.docs.forEach(doc => {
results.push(renderTracks(doc))
}
)
setPBS1Detail(results)
});
}, [])
const renderTracks = (doc) => {
return doc.data().data[0].Module;
}
This way you're only setting state once and thus avoiding unnecessary re-renders and you're saving all of your docs instead of overwriting them.
I have a function loadListings() in react native app it gets data from real time firebase database and renders it on on page in <Flatlist />
function..
let data = [];
listingRef.orderByChild("created_at").on("value", (snapshot) => {
data = [];
snapshot.forEach((listing) => {
data.push(listing.val());
});
setListings(data);
setLoading(false);
});
};
and finally
useEffect(() => {
loadListings();
}, []);
Can I use it without the effect hook?
Yes you can. Based on official documentation for useEffect hook the [] empty array at the end means what loadListings function will be executed ones when this component will be mounted.
I have two redux queries that pull posts from my Firebase Firestore. The first successfully displays all of the posts of the people I'm following:
export function fetchUsersFollowingPosts(uid) {
return ((dispatch, getState) => {
firebase.firestore()
.collection("posts")
.doc(uid)
.collection("userPosts")
.orderBy("creation", "asc")
.get()
.then((snapshot) => {
const uid = snapshot.query.EP.path.segments[1];
const user = getState().usersState.users.find(el => el.uid === uid);
let posts = snapshot.docs.map(doc => {
const data = doc.data();
const id = doc.id;
return { id, ...data, user }
})
for (let i = 0; i < posts.length; i++) {
dispatch(fetchUsersFollowingLikes(uid, posts[i].id))
}
dispatch({ type: USERS_POSTS_STATE_CHANGE, posts, uid })
})
})
}
The second shows all of my own posts.
export function fetchUserPosts() {
return ((dispatch) => {
firebase.firestore()
.collection("posts")
.doc(firebase.auth().currentUser.uid)
.collection("userPosts")
.orderBy("creation", "desc")
.get()
.then((snapshot) => {
let posts = snapshot.docs.map(doc => {
const data = doc.data();
const id = doc.id;
return { id, ...data }
})
dispatch({ type: USER_POSTS_STATE_CHANGE, posts })
})
})
}
Here's where I currently list the users from the people I follow. But how do I combine them so I can show both my posts and those of the people that I'm following in a single FlatList?
function Feed(props) {
useStatusBar('dark-content');
const [posts, setPosts] = useState([]);
const [refreshing, setRefreshing] = useState(false)
useEffect(() => {
if (props.usersFollowingLoaded == props.following.length && props.following.length !== 0) {
props.feed.sort(function (x, y) {
return y.creation.toDate() - x.creation.toDate();
})
setPosts(props.feed);
setRefreshing(false)
}
}, [props.usersFollowingLoaded, props.feed])
return (
<View style={styles.background}>
{posts.length > 0 ?
<View style={styles.containerGallery}>
<FlatList
refreshControl={
<RefreshControl
refreshing={refreshing}
tintColor="white"
onRefresh={() => {
setRefreshing(true);
props.reload()
}}
/>
}
showsVerticalScrollIndicator={false}
numColumns={1}
horizontal={false}
data={posts}
renderItem={({ item }) => (
<View style={styles.containerImage}>
<Card title={item.title} onPress={() => props.navigation.navigate(routes.GOOD_STUFF_DETAIL, { item: item, postId: item.id, uid: item.user.uid, user: item.user,})} showLike={true} author={"Recommended by " + item.user.name} likeItem={item} likeCount={item.likesCount} icon={categories.categories[item.categoryID].icon} timeStamp={timeDifference(new Date(), item.creation.toDate())}/>
</View>
)}
/>
</View>
: <NothingHere title="Follow friends" text="To see their Good Stuff here" icon="search" color="white"/> }
</View>
)
}
const mapStateToProps = (store) => ({
currentUser: store.userState.currentUser,
following: store.userState.following,
feed: store.usersState.feed,
usersFollowingLoaded: store.usersState.usersFollowingLoaded,
})
const mapDispatchProps = (dispatch) => bindActionCreators({ reload }, dispatch);
export default connect(mapStateToProps, mapDispatchProps)(Feed);
Below is my data structure:
Thanks for reading!
Maybe I misunderstood the database structure but AFAIK it doesn't seem possible to combine both queries. From what I see of the database structure you want to retrieve the posts of userB where you (userA) gave a "like". In order to get that information you scan across the path posts/{userUID}/userPosts/{docId}/likes. Given the queries scan different collection ranges I don't see a way to mix them.
Separately, let's assume for the sake of the argument that you already have a list containing the user UIDs of the people you follow. Then, the Firestore query feature that gets closer to the desired behavior are Collection Group queries. Having in mind the structure of the database:
posts
uid1
userPosts
doc1
doc2
uid2
userPosts
docX
docY
Essentially, collection group queries are a way to simultaneously query across all userPosts collections at once. If each document were to have the author ID as a field you would be able to do something like this:
db.collectionGroup("userPosts").where("uid", "in", ListOfFollowedUsers)
This won't totally solve the problem because the in operator clause is limited to 10 values, so you may apply it at most for 10 followed users.
Overall I would suggest to keep the queries separated and merge them in the application code.
Based on your data, and knowing for a fact how limited queries on Firestore are, you need to do that merge on the client.
What I would do is to keep the list on redux and handle the merge on the reducer. You just need to listen to both actions and then merge them as an array on your app.
If you want to avoid the user seeing partial data (eg, you show your own posts and then another refresh just adds your followers post thus the UI will change) you might want to keep two flags (booleans) while you are loading data and show a spinner if both lists haven't been loaded.
Unless you want to refactor your code, then a lot of merging happens on the client.
Also, side node, I would NOT dispatch something on a for loop like you are doing on the first one, because if that triggers a firestore request, then it might get expensive real fast.
So, I have a Firestore database group like so.
companies > acme-industries > items > []
OR
collection > document > collection > document
Would it be better to just store all items inside a base collection and then add a string value to each item that defines what company it goes too? Then just query the items collection for all items linked to that company?
I am trying to retrieve the items and run them through a forEach in my firebase function. I have tried two different approaches and watched multiple videos and still am not getting results.
First Attempt Code Block
This resulted in a 500 Server Error with no explanation returned.
const itemQuerySnapshot = db.collection('companies').doc(data.userData.company).collection('items').get();
const items: any = [];
itemQuerySnapshot.forEach((doc:any) => {
console.log('doc', doc.data());
items.push({
id: doc.id,
data: doc.data()
});
});
response.json(items);
Second Attempt Code Block
This resulted in the No Such Documents! being returned
const itemRef = db.collection('companies').doc(data.userData.company).collection('items');
itemRef.get().then((doc:any) => {
if(!doc.exists) {
response.send('No such documents!');
} else {
response.send('Document Data: '+ doc.data());
}
}).catch((err:any) => {
response.status(500).send(err);
});
I am expecting something like an array of all the items to be returned from this call. I'm completely new to Firebase Firestore, what am I missing here?
UPDATE
I replaced my code with a third attempt code block and I got success with the console.log(doc.data()). However, the items object still returns empty. Is this because it's returning before the for each is done? If so, how would you prevent that to ensure every item that should be returned is?
const items: any = [];
const userRef = db.collection("companies").doc(data.userData.company);
const itemsRef = userRef.collection("items");
itemsRef
.get()
.then((snapshot: any) => {
snapshot.forEach((doc: any) => {
console.log(doc.data());
items.push({
id: doc.id,
data: doc.data()
});
});
})
.catch((err: any) => {
response.status(500).send(err);
});
response.json(items);
How would you add one more document into the mix? Say you want to get a single item. How would you do that? The following always results in Item does not exist being returned from my function.
const companyRef = db.collection('companies').doc(data.userData.company);
const itemRef = companyRef.collection('items');
const item = itemRef.where('number', '==', itemSku).get();
I must be doing something incredibly wrong here because all the videos are telling me it's incredibly easy to fetch data from Firestore. But I have yet to see that.
get returns a Promise , the callback of then function will be called once the data ready from firestore .
the line response.json(items); will be called before the items array collected correctly.
you need to move this line inside the then callback
checkout this :
.then((snapshot: any) => {
snapshot.forEach((doc: any) => {
console.log(doc.data());
items.push({
id: doc.id,
data: doc.data()
});
});
response.json(items); //items ARRAY IS READY , YOU CAN SEND YOUR RESPONSE HERE
})
I am using React Native and Firebase Realtime Database. I am experiencing two problems with the FlatList component:
I am getting a lot of "duplicate key" errors whenever the list re-renders. I am not sure why I am getting this problem because I am setting the key of every item in my list as the snap.key value generated by Firebase, which is unique (and I have verified this in my logs).
The list sometimes does not re-render, even when I scroll up or down on it. This "sometimes" behavior is throwing me off, and I have not been able to debug it. I am using the ".on" method for getting my list from the Firebase Realtime Database.
This is the code that I am using:
export default class FlatListPage extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
data: [],
};
}
makeRemoteRequest = () => {
var items = [];
DB.on('value', (snap) => {
this.getItems(snap, items);
items = items.reverse();
this.setState(
{data: items}
);
console.log(this.state.data); //checking key properties are unique
});
}
getItems = (snap, items) => {
snap.forEach((child) => {
items.push({
key: child.key,
status: child.val().status,
location: child.val().location,
});
});
}
componentWillMount(){
this.makeRemoteRequest();
}
render() {
return (
<View>
<FlatList
data={this.state.data}
renderItem={({item}) => <MyListItem item={item} />}
/>
</View>
);
}
}
You're receiving duplicate keys because you're not resetting the items array when receiving a new value event from firebase. This means you're simply re-adding the same items over and over again.
Update your makeRemoteRequest method to recreate the array each time you get a value event as follows:
makeRemoteRequest = () => {
DB.on('value', (snap) => {
var items = [];
this.getItems(snap, items);
items = items.reverse();
this.setState(
{data: items}
);
console.log(this.state.data); //checking key properties are unique
});
}
I'm not sure about number 2 - it might be that the above fixes it as a side effect.
instead of getting the whole list every time I suggest to get all elements .once and then use 'child_added' event and .limitToLast(1) to get only new items and add them to your array. Also, I noticed that you are pushing items to the array and then reversing it. You can use .unshift method that inserts items at the beginning of the array so you do not have to reverse it later.