Update 2 children in firebase realtime database with react native - javascript

Im trying to update two children in my database (using realtime database firebase), but when database is updated, my application go back to the home screen for no reason.
When I update only in "Tasks" it works (the app doesnt go back to the home screen) but when I combine update in "Tasks" and "Users" there is this problem..
Maybe i dont do it the good way.. Any ideas?
statusPlayback = async (status) => {
const { navigation } = this.props
const task = navigation.getParam('task')
//console.log("task = ", task);
//to check if we arrived to the end of the
if (status.didJustFinish) {
const CountVideoRef = firebase
.database()
.ref("Tasks")
.child(firebase.auth().currentUser.uid).child(task.AssignPerson)
.child(task.taskname);
CountVideoRef.once("value").then((snapshot) => {
CountVideoRef.update({
countViewVideo: snapshot.val().countViewVideo + 1,
});
})
const PointEndVideoRef = firebase
.database()
.ref("Users")
.child(firebase.auth().currentUser.uid);
PointEndVideoRef.once("value").then((snapshot1) => {
PointEndVideoRef.update({
Points: snapshot1.val().Points + 10,
});
const points = (snapshot1.val().Points) + 10
//console.log("points = ", points)
this.props.updatePoints({ points: points })
})
this.setState({ showButtonVisible: true });
}
};

I doubt this is the cause of the navigation problem, but this style of updating is fraught with problems:
CountVideoRef.once("value").then((snapshot) => {
CountVideoRef.update({
countViewVideo: snapshot.val().countViewVideo + 1,
});
})
If you need to update an existing value in the database based on its existing value, you should use a transaction to prevent race conditions when multiple users perform the same action around the same time (or while offline).
In this case, you can use the simpler atomic server-side increment operation, which means the above becomes:
CountVideoRef.set(firebase.database.ServerValue.increment(1));

Related

I want to pass user View time of a video to server using an api call on visibilityChange event in react

I want to pass the watch time of a video the user has seen when user closes the page,reload the page or navigate to another page. I am using visibilityChange event for this. When i try to navigate to another page, the api call runs perfectly. But the data i am sending to the api is not updated correctly. I am going to provide the code and the output below so you can understand perfectly what my problem is.
useEffect(async () => {
const x = 0;
console.log("use effect is run number ::::", x + 1);
window.addEventListener("visibilitychange", sendViewTime);
return () => {
window.removeEventListener("visibilitychange", sendViewTime);
};
}, []);
I have added the event listener in the useEffect.
the sendViewTime method is the method i want to call on visibility change event. This Method is working perfectly but for some reason the params are not updated even though i have set their states in their relavant hooks.
const sendViewTime = async () => {
if (document.visibilityState === "hidden") {
console.log("the document is hidden");
const value = localStorage.getItem("jwt");
const initialValue = JSON.parse(value);
console.log("the send View Time is :::", played_time);
const params = {
video_id: video_id_url,
viewTime: played_time,
MET: MET_value,
weight: "",
};
console.log("params are :::", params);
await setEffort(params, initialValue).then((res) => {
console.log("set effort api response is ::: ", res);
});
} else {
console.log("the document is back online");
}
};
//This onProgress prop is from react Player. Here i am updating the state of video progress.
onProgress={(time) => {
console.log("the time is :::", time);
const time_1 = Math.round(time.playedSeconds);
const time_2 = JSON.stringify(time_1);
setPlayed_time(time_2);
console.log("the played time is :::", played_time);
}}
//OUTPUT
// the document is hidden.
// the send View Time is :::
//params are ::: {video_id: '23', viewTime: '', MET: undefined, weight: ''}
//set effort api response is ::: {status: 200, message: 'Success', data: {…}, time: '2.743 s'}
//the document is back online
Never mind guys. I found the solution. It seems that i have to pass played_time and met value as a prop to the useEffect.If you want to know how useEffect works please visit this link. In general is it better to use one or many useEffect hooks in a single component?.

Paginating data causing unusual behavior?

I am displaying "global posts" on one of my tabs. Currently, there are only 11 posts in the database:
In the app Some of the posts are being duplicated, and I have no idea why these SPECIFIC posts are being duplicated, as it seems to me like it is happening at random.
Here is the code for how I paginate the data.
When the component mounts, I query firestore and pull 5 posts using getCollection().
.
async componentDidMount() {
this.unsubscribe = Firebase.firestore()
.collection('globalPosts')
.orderBy("date_created", "desc")
.limit(5)
.onSnapshot(this.getCollection);
}
I get the posts successfully in getCollection(), and set an index, lastItemIndex, so I know where to query for the next posts
.
getCollection = (querySnapshot) => {
const globalPostsArray = [];
querySnapshot.forEach((res) => {
const {
..fields
} = res.data();
globalPostsArray.push({
..fields
});
});
this.setState({
globalPostsArray,
isLoading: false,
lastItemIndex: globalPostsArray.length - 1
});
}
This gets the first 5 items, no problem, ordered by date_created, descending.
If the user scrolls down the flatlist, I have logic in the flatlist to handle fetching more data:
<FlatList
data={this.state.globalPostsArray}
renderItem={renderItem}
keyExtractor={item => item.key}
contentContainerStyle={{ paddingBottom: 50 }}
showsHorizontalScrollIndicator={false}
showsVerticalScrollIndicator={false}
onRefresh={this._refresh}
refreshing={this.state.isLoading}
onEndReachedThreshold={0.5} <---------------------- Threshold
onEndReached={() => {this.getMore()}} <------------ Get more data
/>
Finally, once it is time to retrieve more data, I call this.getMore()
Here is the code to get the next 5 posts:
getMore = async() => {
const newPostsArray = [] <-------- new array for the next 5 posts
Firebase.firestore()
.collection('globalPosts')
.orderBy("date_created", "desc")
.startAfter(this.state.globalPostsArray[this.state.lastItemIndex].date_created) <--- note start after
.limit(5)
.onSnapshot(querySnapshot => {
querySnapshot.forEach((res) => {
const {
... same fields as getCollection()
} = res.data();
newPostsArray.push({
... same fields as getCollection()
});
});
this.setState({
globalPostsArray: this.state.globalPostsArray.concat(newPostsArray), <--- add to state array
lastItemIndex: this.state.globalPostsArray.length-1 <---- increment index
});
console.log(this.state.lastItemIndex) <------- I print out last item index
})
}
Some notes:
The code works fine in terms of fetching the data
The code works fine in terms of pagination, and only fetches 5 posts at a time
There is no discernible pattern I am seeing in which posts are being duplicated
I am ordering by date_created, descending when querying firestore in both getCollection() and getMore()
I console log "last item index" in my getMore(), and of course the index is higher than the number of posts
I keep getting the following warning/error, with different keys (post ID's in firestore), which shows me the duplication is happening at random, and not specific to one user. This warning/error doesn't break the application, but is telling me this weird behavior is happening:
Encountered two children with the same key, ZJu3FbhzOkXDM5mn6O6T. Keys should be unique so that components maintain their identity across updates. Non-unique keys may cause children to be duplicated and/or omitted — the behavior is unsupported and could change in a future version.
Can someone point me in the right direction, why my pagination is having such unusual behavior?
My issue was with lastItemIndex. Saving it in state was causing problems. I solved the problem by removing lastItemIndex from state, and making it a local variable in getMore():
getMore = async() => {
const newPostsArray = []
const lastItemIndex = this.state.globalPostsArray.length - 1 <---- added it here
await Firebase.firestore()
.collection('globalPosts')
.orderBy("date_created", "desc")
.startAfter(this.state.globalPostsArray[lastItemIndex].date_created)
.limit(5)
.onSnapshot(querySnapshot => {
querySnapshot.forEach((res) => {
const {
..fields
} = res.data();
newPostsArray.push({
key: res.id,
..fields
});
});
this.setState({
globalPostsArray: this.state.globalPostsArray.concat(newPostsArray)
});
})
}
You can only guarantee uniqueness on a document ID, not by the contents of a document's fields.
For example, as a workaround, you could concatenate two IDs into a single string and use it as the document ID. You would then use a transaction to ensure that a document does not already exist with that new composite ID before writing it.
In order to paginate through a list of these unique items you need to use .startAfter() and .limit(). You can then create a lastVisible variable that uses the last document in a batch to start a cursor for the next.
var lastVisible = documentSnapshots.docs[documentSnapshots.docs.length-1];
The example below can be found here if you want to read more about it:
var first = db.collection("cities")
.orderBy("population")
.limit(25);
return first.get().then(function (documentSnapshots) {
// Get the last visible document
var lastVisible = documentSnapshots.docs[documentSnapshots.docs.length-1];
console.log("last", lastVisible);
// Construct a new query starting at this document,
// get the next 25 cities.
var next = db.collection("cities")
.orderBy("population")
.startAfter(lastVisible) // <--- HERE
.limit(25);
});

Firestore Delete All References of A User

I have a chat app build in react native. When a user decides to delete their profile, I want to remove all references of them from the database.
The DB has references to their user id in the "matches" table, the "chat" table, and the "messages" table for each of the people the deleted user was chatting with.
I am using firebase functions to handle the deletion of the user doc data and auth but I am not sure what the best way to go about removing all of these references would be. My question is: what is the best way to remove all references of an ID out of a somewhat complex database? I assume this will be taxing to loop through every single user in the DB to search for this one ID.
deleteAccount = () => {
var db = firebase.firestore();
firebase.auth().onAuthStateChanged(async (user) => {
if (user) {
//delete user data
db.collection("Users")
.doc(user.uid)
.delete();
} else {
console.log("user needs to reauth");
return false;
}
});
};
firebase functions
exports.deleteUser = functions.firestore
.document("Users/{userID}")
.onDelete((snap, context) => {
const deletedValue = snap.data();
// Delete the images
try {
admin.auth().deleteUser(deletedValue.id);
const imgRef1 = firebase.storage().ref(user.uid + "/images/0")
? firebase.storage().ref(user.uid + "/images/0")
: null;
const imgRef2 = firebase.storage().ref(user.uid + "/images/1")
? firebase.storage().ref(user.uid + "/images/1")
: null;
const imgRef3 = firebase.storage().ref(user.uid + "/images/2")
? firebase.storage().ref(user.uid + "/images/2")
: null;
imgRef1.delete().then(function() {
imgRef2.delete().then(function() {
imgRef3.delete().then(function() {});
});
});
} catch (e) {
console.log("no images to delete");
}
});
Firebase products such as the databases and storage have no implicit knowledge of what data belongs to what user. That relation only exists because your application code made it.
For that reason you will also have to look up/traverse the relations when deleting the user, to find (and delete) their data. There are no shortcuts in the product here, although there is a open-source library that contains an implementation that works from a configuration file: user-data-protection
Edit: I just realized there's actually an Extension to Delete User Data, which does pretty much the same as the library linked above. It might be worth to have a look if that suits your needs

How to move Firebase relational queries server side

This is my first project with Firebase, and I've created a relational data structure. Now I see why this isn't the best way to do things!
In this part of my app, users can add multiple items to an outfit - here's a diagram of the data structure/relationship I have in Firebase now.
I've included code from a Redux action creator in my React Native app. When a user edits an outfit - removing some items - this code:
takes an array with the new list of items from the client
compares this array with the current saved items server side
creates a new array of the diff (removed items)
loops through the outfits for each of those items, matching against the outfit being edited
removes references that match
This code works, but is pretty deeply nested and messy:
export const updateTagReferences = (localTags, outfitId) => {
//localTags represents the new set of items from application state
//outfitId is the uid for the outfit where those items appear
const {currentUser} = firebase.auth();
return dispatch => {
firebase
.database()
.ref(`users/${currentUser.uid}/outfits/${outfitId}/taggedItems`)
.once('value')
.then(snapshot => {
var serverTags = snapshot.val();
// Work out the diff (ie, which items have been removed locally)
return _.differenceWith(serverTags, localTags, _.isEqual);
})
.then(toDelete => {
toDelete.map(item => {
firebase
.database()
.ref(`users/${currentUser.uid}/items/${item.item.uid}/outfits`)
.once('value')
.then(snapshot => {
var outfits = snapshot.val();
for (var taggedItem in outfits) {
if (outfits.hasOwnProperty(taggedItem)) {
var i = outfits[taggedItem];
return i.uid === outfitId
? firebase
.database()
.ref(
`users/${currentUser.uid}/items/${item.item.uid}/outfits/${taggedItem}`,
)
.remove()
: null;
}
}
});
});
})
.then(console.log('Done'));
};
};
As you can see, I'm trying to use Firebase promises to loop through the outfits nested in each item server side.
My issues are:
I'm making lots of queries to Firebase, which isn't ideal
console.log('Done') fires before .remove() - I need to follow up with a second action once this action completes
I've tried the following code, based on this answer, but I can't seem to get it to work:
export const updateTagReferences = (localTags, outfitId) => {
const {currentUser} = firebase.auth();
return dispatch => {
// Create a ref for the outfit the user is editing
firebase
.database()
.ref(`users/${currentUser.uid}/outfits/${outfitId}/taggedItems`)
.once('value')
.then(snapshot => {
// Compare local (app state) with remote (Firebase) to work out which items have been removed
var serverTags = snapshot.val();
return _.differenceWith(serverTags, localTags, _.isEqual);
})
.then(toDelete => {
var promises = [];
// Create a ref for each item to delete
toDelete.map(item => {
firebase
.database()
.ref(`users/${currentUser.uid}/items/${item.item.uid}/outfits`)
.once('value')
.then(snapshot => {
var outfits = snapshot.val();
for (var taggedItem in outfits) {
// Loop through the outfits that item appears in
if (outfits.hasOwnProperty(taggedItem)) {
var i = outfits[taggedItem];
// Match against the outfit the user is editing
return i.uid === outfitId
? promises.push(
firebase
.database()
.ref(
`users/${currentUser.uid}/items/${item.item.uid}/outfits/${taggedItem}`,
)
// Remove the reference
.remove(),
)
: null;
}
}
})
// Execute all the promises, removing firebase references for each item removed locally
.then(Promise.all(promises).then(console.log('Done')));
});
});
};
};
I'm trying to make my code more efficient overall, so I can replicate it elsewhere in my project. I'd love an explanation of the best way to move this kind of query server side.
Currently, changing the whole data structure is out of scope (that's for next time!)
Thanks.

Async issue with State in React Native

I'm trying to build a simple app that lets the user type a name of a movie in a search bar, and get a list of all the movies related to that name (from an external public API).
I have a problem with the actual state updating.
If a user will type "Star", the list will show just movies with "Sta". So if the user would like to see the actual list of "Star" movies, he'd need to type "Star " (with an extra char to update the previous state).
In other words, the search query is one char behind the State.
How should it be written in React Native?
state = {
query: "",
data: []
};
searchUpdate = e => {
let query = this.state.query;
this.setState({ query: e }, () => {
if (query.length > 2) {
this.searchQuery(query.toLowerCase());
}
});
};
searchQuery = async query => {
try {
const get = await fetch(`${API.URL}/?s=${query}&${API.KEY}`);
const get2 = await get.json();
const data = get2.Search; // .Search is to get the actual array from the json
this.setState({ data });
} catch (err) {
console.log(err);
}
};
You don't have to rely on state for the query, just get the value from the event in the change handler
searchUpdate = e => {
if(e.target.value.length > 2) {
this.searchQuery(e.target.value)
}
};
You could keep state updated as well if you need to in order to maintain the value of the input correctly, but you don't need it for the search.
However, to answer what you're problem is, you are getting the value of state.query from the previous state. The first line of your searchUpdate function is getting the value of your query from the current state, which doesn't yet contain the updated value that triggered the searchUpdate function.
I don't prefer to send api call every change of letters. You should send API just when user stop typing and this can achieved by debounce function from lodash
debounce-lodash
this is the best practise and best for user and server instead of sending 10 requests in long phases
the next thing You get the value from previous state you should do API call after changing state as
const changeStateQuery = query => {
this.setState({query}, () => {
//call api call after already changing state
})
}

Categories