Deleting document from Firestore based on array key - reactjs

I'm making a web app to help people create graphs. When a user creates two graphs and deletes the first one, the index in the array changes to 0 and so the second graph (graph1) doesn't get deleted from Firestore. Any ideas on how to approach this? Thanks
Adds Graph
onClick={ () => {
const clientDb = firebaseClient.firestore();
// Adding Graph Options NOTICE HERE SITTING DOCUMENT NAME TO graph${i}
for(var i = 0 ; i < numberofGraphs.length ; i++ ){
clientDb.collection("Users").doc(props.uid).collection("Dashboard").doc(`graph${i}`).set({
type:numberofGraphs[i].type,
title:numberofGraphs[i].type,
seriestitle:numberofGraphs[i].seriestitle,
legend:numberofGraphs[i].legend,
xAxis:numberofGraphs[i].xAxis,
yAxis:numberofGraphs[i].yAxis,
color:numberofGraphs[i].color,
tooltipcolor:numberofGraphs[i].tooltipcolor,
tooltiptextcolor:numberofGraphs[i].tooltiptextcolor,
axisColor:numberofGraphs[i].axisColor,
})
}
}}
Deletes Graph
numberofGraphs.map( (si, k) => (
<>
<CloseIcon
onClick={ () => {
if(window !== "undefined") {
console.log("lets see it")
const clientDb = firebaseClient.firestore();
//NOTICE HERE DELETING Graph with index from map
clientDb.collection("Users").doc(props.uid).collection("Dashboard").doc(`graph${k}`).delete();
}
const newgraphs = numberofGraphs.filter( (object, kk) => k!== kk )
setnumberofGraphs(newgraphs);
}}
/>
<CreateGraph2 type={si.type} title={si.title} seriestitle={si.seriestitle}/>
</>
))

If you absolutely have to do it this way you could "mark doc as deleted" by doing collection('Dashboard').doc('<doc-to-delete>').set({ deleted: true }) and then just filter it out in the client by this property and don't display it.
More generally - use collection().add() to create new documents and let firestore auto-generate IDs for you. Then access your documents by ID, instead of trying to keep track of indices on the front end.

I solved my issue doing the following:
Adds Graph
// Took #samthecodingman's advice by moving all graphs to their own /Graphs collection.
// Which also resonated with #Brian's answer to use
// collection().add() to add documents with Auto-generated ID's instead of adding graphs based
// on index no. of array.
onClick={ () => {
if(window !== "undefined") {
const clientDb = firebaseClient.firestore();
clientDb.collection("Users").doc(props.uid)
.collection("Dashboard")
.doc("First").collection("Graphs").add({
type:type, title:title, seriestitle:seriestitle,
legend:legend,
xAxis:xAxis,
yAxis:yAxis,
color:color,
tooltipcolor:tooltipcolor,
tooltiptextcolor:tooltiptextcolor,
axisColor:axisColor,
//passed an id filed to the object I'm saving
id:type+title
})
}
}}
Deletes Graph
//mapping through an array of objects (si) and then using the get() method with
a query to check for matching ID. Then used the id in the delete method
if(window !== "undefined") {
const clientDb = firebaseClient.firestore();
const docref = clientDb.collection("Users").doc(props.uid)
.collection("Dashboard").doc("First").collection("Graphs");
docref.where("id" , "==", `${si.type}${si.title}`)
.get()
.then((querySnapshot) => {
querySnapshot.forEach((doc) => {
docref.doc(doc.id).delete()
console.log(doc.id, " => ", doc.data() );
});
})
}

Related

Nested onSnapshot problem in react-native-Firebase Firestore

I'm struggling to overcome problems that I have on nested onSnapshot, so, in short, I have 3 nested onSnapshot and when parent/root onSnapshot updates it also creates new onSnapshot listeners and leaves old ones too, so old and new ones are listening to the changes. I know that I should unsubscribe it but I can't, I'm losing track of which listeners are added or already exist.
One solution is to create array of unsubscribing functions in parent onSnapshot, but there another problem comes, I'm using docChanges with forEach and it is hard to manage which would I unsubscribe from array.
I saw this on Stackoverflow but it doesn't fit mine or even doesn't explain correctly exactly this case: nested listeners
What can you suggest to me? I don't know what else should I do.
Thanks in advance.
here is an example of my code that I'm trying to implement unsubscribe stuff( I use Mobx):
// TODO: optimise this query
const id = auth().currentUser?.uid;
// get all channelId-s that user has
channelParticipantsRef
.where('user.id', '==', id)
.onSnapshot((userChannels) => {
userChannels?.docChanges().forEach(function (channelParticipant) {
const channelParticipantData = channelParticipant;
if (!channelParticipantData.doc.exists) return;
// get all channels data that user has
channelsRef
.where(
'channelId',
'==',
channelParticipantData.doc.data().channelId,
)
.onSnapshot((channels) => {
if (!channels || channels.empty) return;
channels.docChanges().forEach(function (channel) {
const channelDataObject = channel.doc.data();
console.logBeauty(channel.type, 'channelDataObject ');
if (channel.type === 'added' || channel.type === 'modified') {
const channelData: ChannelTransformedDataType = {
...channelDataObject,
language: channelParticipantData.doc.data().language,
channelParticipantId: channelParticipantData.doc.id,
lastMessageDate: {
...channelDataObject.lastMessageDate,
},
otherUsers: [],
};
// get all channels users
channelParticipantsRef
.where('user.id', '!=', id)
.where('channelId', '==', channelDataObject.channelId)
.onSnapshot((channelParticipants) => {
const participants: UserModelType[] = [];
if (channelParticipants.empty)
return self.removeChannel(
channelDataObject.channelId,
);
channelParticipants.docs.forEach(
(channelParticipant) => {
participants.push(channelParticipant.data().user);
},
);
channelData.otherUsers = participants;
console.log(channelData, 'channelDatachannelData');
if (channel.type === 'added')
self.pushChannel(channelData);
else self.editChannel(channelData);
});
} else if (channel.type === 'removed') {
self.removeChannel(channelDataObject.channelId);
}
});
});
});
});

React not re-rendering when state changed from hook

Edit: Thanks for the help everyone. I needed to change the reference of the array and fixed it by doing:
setData([...sorted])
I am currently rendering out a list of tasks. This is a snippet of my return function within a functional component:
const [ data, setData ] = useState( mockData )
<tbody>
{ data.map(d => <TaskItem key={d.claimable} task={d}/>) }
</tbody>
When I click on a certain button on the page, the dataset gets sorted and I call setData(sortedData)
For some reason, the table isnt being re-rendered with the sorted data. Is there something I did wrong here?
This is the sort function:
function filterByContactAmount():void {
let sorted = data.sort((a:any, b:any) => {
let aTimesContacted:number = a.data.person.contact.list.reduce((acc:number, val:any):number => acc + val.history.length, 0)
let bTimesContacted:number = b.data.person.contact.list.reduce((acc:number, val:any):number => acc + val.history.length, 0)
if ( aTimesContacted > bTimesContacted ) {
return 1
}
if ( bTimesContacted > aTimesContacted ) {
return -1
}
return 0;
})
console.log(sorted)
setData(sorted)
}
Its because you are using the same ref of the array, you need set the new data with
setData(old => "sorted data");
to change the reference of the state and it updates
function filterByContactAmount():void {
let sorted = data.sort((a:any, b:any) => {
let aTimesContacted:number = a.data.person.contact.list.reduce((acc:number, val:any):number => acc + val.history.length, 0)
let bTimesContacted:number = b.data.person.contact.list.reduce((acc:number, val:any):number => acc + val.history.length, 0)
if ( aTimesContacted > bTimesContacted ) {
return 1
}
if ( bTimesContacted > aTimesContacted ) {
return -1
}
return 0;
})
console.log(sorted)
setData(old => [...sorted]) // Sorted is the new state sorted
}
You are mutating sate, the other answer is probably not the best because you are still mutating state and then setting state with a copy of the already mutated value.
The sort function can also be optimized. Maybe try the following:
function filterByContactAmount() {
let sorted = data
.map(d => ({//map shallow copies the array
...d,//shallow copies the item
sortedNum: d.data.person.contact.list.reduce(//do this once for every item, not for every time sort callback is called
(acc, val) => acc + val.history.length,
0
),
}))
.sort((a, b) => a.sortedNum - b.sortedNum);
console.log(sorted);
setData(sorted);
}
I think issue is located under d.claimable, I suppose it is boolean variable type. You must know that every key prop must be unique. Check if you have for example.id property, if not add it.
Uniqueness of key prop is very important during reconciliation process.
Unique identifier with a group of children to help React figure out which items have changed, have been added or removed from the list. It’s related to the “lists and keys” functionality of React described here.
Very nice article about reconciliation.
<tbody>
{ data.map(d => <TaskItem key={d.claimable} task={d}/>) }
</tbody>

Firestore - Simple full text search solution

I know that firestore doesn't support full text search and it giving us solution to use third party services. However I found a simple solution to simple "full text search" and I think this might help others who doesn't want to use third party services as me for such a simple task.
I'm trying to search for company name which is saved in firestore collection under my companyName which can be in any format for example "My Awesome Company". When adding new company with companyName or updating a value in companyName I'm also saving searchName with it which is the same value as company name but in lower case without spaces
searchName: removeSpace(companyName).toLowerCase()
removeSpace is my simple custom function which remove all spaces from a text
export const removeSpace = (string) => {
return string.replace(/\s/g, '');
}
That turns our company name to myawesomecompany which is saved in searchName
Now I've got a firestore function to search for company which indexing through searchName and returning companyName. Minumum search value is a searched value without last character and maximum search value is a searched value with added "zzzzzzzzzzzzzzzzzzzzzzzz" transformed to lower case. That means if you search for My Aw then min value will be mya and max value will be myawzzzzzzzzzzzzzzzzzzzzzzz
exports.handler = ((data) => {
const searchValue = data.value.replace(/\s/g, '').toLowerCase()
const minName = searchValue.substr(0, searchName.length-1)
const maxName = searchValue + "zzzzzzzzzzzzzzzzzzzzzzzz"
let list = []
const newRef = db.collection("user").where("profile.searchName", ">=", minName).where("profile.searchName", "<=", maxName)
return newRef.get()
.then(querySnapshot => {
querySnapshot.forEach(doc => {
list.push({ name: doc.data().profile.companyName})
})
return list
})
})
I didn't have time to fully test it but so far it works without any problems. Please let me know if you spot anything wrong with it. Now the question is
Is "z" character the highest value character in firestore or is there any other more decent way to add into the search value maximum amount without adding "zzzzzzzzzzzzz"?
I like your decision to preprocess the text so that it can be queried, but you could provide for a more flexible search by storing lowercase keywords with the users and searching those. In other words, transform:
"My Awesome Company"
to...
{ my: true, awesome: true, company: true }
...and test against that.
When adding/updating the property:
// save keywords on the user
let keywords = {}
companyName.split(' ').forEach(word => keywords[word.toLowerCase()] = true)
When querying:
let searchKeywords = userInputString.split(' ').map(word => word.toLowerCase())
let collection = db.collection("user")
searchKeywords.forEach(keyword => {
collection = collection.where(`keywords.${keyword}` , '==' , true);
});
With a little modification of previous answer I have made another simple text search. I'm saving keyword to an array instead of saving it in object like this
nameIndex: textIndexToArray(companyName)
where textIndexToArray is my custom function
export const textIndexToArray = (str) => {
const string = str.trim().replace(/ +(?= )/g,'')
let arr = []
for (let i = 0; i < string.trim().length; i++) {
arr.push(string.substr(0,i+1).toLowerCase());
}
return arr
}
which transfer a text into array. For example
"My Company"
will return
[m, my, my , my c, my co, my com, my comp, my compa, my compan, my company]
with nameIndex saved in firestore we can simply query the data thorough nameIndex and return companyName
exports.handler = ((data) => {
const searchValue = data.value.toLowerCase()
let list = []
const newRef = db.collection("user").where("nameIndex", "array-contains", searchValue)
return newRef.get()
.then(querySnapshot => {
querySnapshot.forEach(doc => {
list.push({ name: doc.data().companyName, })
})
return list
})
})

How can I reverse data array of Firestore ? Angular 6

listItemAds() {
this.itemList = this.afs.collection<Class>('Collection');
this.itemList.snapshotChanges().subscribe(list => {
this.itemArray = [];
list.forEach(action => {
const data = action.payload.doc.data() as Class;
this.itemArray.push(data);
--> this.itemArray.reverse(); // I try use this.itemArray.reverse(data.idNumber) ==> error
});
});
console.log(this.itemArray);
}
I wanna reverse data from New data to Old data , I set idNumber in object ex:[1,2,3,4,5].
Note
You can use the orderBy query option in your collection call.
The Firebase documentation on ordering & limiting shows you how to use orderBy specifically.
Using the server to sort data results in better client performance.
this.itemList = this.afs.collection<Class>(
'Collection',
ref => ref.orderBy('idNumber', 'desc')
);
this.itemList.subscribe(list => {
console.log(list);
});

Best way to remove a specific item in AsyncStorage

I'm storing an array of objects in AsyncStorage and would like to know the best way to remove a specific object. Right now I´m passing an id to my function and then I loop through the array to match the id and remove the object and then the array in AsyncStorage is updated. This seems to work ok, but I wonder if this is optimal or if there is a better way to do this?
My function right now:
export function removeData(id) {
AsyncStorage.getItem('#books')
.then((books) => {
const updatedBooks = (JSON.parse(books))
for (let i = 0; i < updatedBooks.length; i++) {
if(updatedBooks[i].id == id) {
updatedBooks.splice(i, 1);
}
}
AsyncStorage.setItem('#books', JSON.stringify(updatedBooks));
})
}
My function for adding data to AsyncStorage:
export function addData(book) {
AsyncStorage.getItem('#books')
.then((books) => {
const b = books ? JSON.parse(books) : [];
b.push(book);
AsyncStorage.setItem('#books', JSON.stringify(b));
});
}
Button to add data with sample data to show structure:
<Button
title = "Add book"
onPress={() => addData({
id: 1,
title: 'Harry Potter',
author:'J.K. Rowling',
thumbnail:'https://covers.openlibrary.org/w/id/7984916-M.jpg',
})
To remove single item
AsyncStorage.removeItem('key', (err) => {
// key 'key' will be removed, if they existed
// callback to do some action after removal of item
});
To remove multiple items
let keys = ['k1', 'k2'];
AsyncStorage.multiRemove(keys, (err) => {
// keys k1 & k2 removed, if they existed
// callback to do some action after removal of item
});
Reference:
RemoveItem method
MultiRemove method

Resources