Preventing duplicates inserted when using Observable Angular 2 - javascript

I have an issue where my array is exponentially growing more and more each time I submit a post. I think it is happening in the second observable as that user object is being updated after each post to update the timestamp for when they last updated a post.
I am trying to check on the inside observable if that post is already in the array to prevent the duplicates from being inserted to the array. For some reason this is not working.
loadPosts(url: string) {
switch (url) {
case '/timeline/top':
this.postsService.subscribeAllPosts(this.selectedArea)
.subscribe(posts => {
let container = new Array<PostContainer>();
for (let post of posts) {
this.getEquippedItemsForUsername(post.username).subscribe(x => {
try {
if (container.indexOf(new PostContainer(post, x[0].equippedItems)) === -1) {
container.push(new PostContainer(post, x[0].equippedItems));
}
console.log( container); // grows exponentially after each submitted post
} catch (ex) { }
}
);
}
this.postContainers = container; // postContainers is the array that is being looped over in the html.
});
break;
}
}

I'm not sure whether you are correct about the problem, removing duplicates from your posts would be easy like this:
this.postsService.subscribeAllPosts(this.selectedArea)
.distinct()
.subscribe(posts => {
...
});

Your problem is that by creating a new PostContainer you're creating a new object which is not in container, so it will add every post in posts.
You should instead check that some unique value of post does not exist in any item of container
Something like:
if (container.findIndex((postContainer) => postContainer.id === post.id) === -1) {
continer.push(new PostContainer(post, x[0].equippedItems));
}

Related

Firestore listener removes a message from pagination when adding a new message in React Native

I am trying to do Firestore reactive pagination. I know there are posts, comments, and articles saying that it's not possible but anyways...
When I add a new message, it kicks off or "removes" the previous message
Here's the main code. I'm paginating 4 messages at a time
async getPaginatedRTLData(queryParams: TQueryParams, onChange: Function){
let collectionReference = collection(firestore, queryParams.pathToDataInCollection);
let collectionReferenceQuery = this.modifyQueryByOperations(collectionReference, queryParams);
//Turn query into snapshot to track changes
const unsubscribe = onSnapshot(collectionReferenceQuery, (snapshot: QuerySnapshot) => {
snapshot.docChanges().forEach((change: DocumentChange<DocumentData>) => {
//Now save data to format later
let formattedData = this.storeData(change, queryParams)
onChange(formattedData);
})
})
this.unsubscriptions.push(unsubscribe)
}
For completeness this is how Im building my query
let queryParams: TQueryParams = {
limitResultCount: 4,
uniqueKey: '_id',
pathToDataInCollection: messagePath,
orderBy: {
docField: orderByKey,
direction: orderBy
}
}
modifyQueryByOperations(
collectionReference: CollectionReference<DocumentData> = this.collectionReference,
queryParams: TQueryParams) {
//Extract query params
let { orderBy, where: where_param, limitResultCount = PAGINATE} = queryParams;
let queryCall: Query<DocumentData> = collectionReference;
if(where_param) {
let {searchByField, whereFilterOp, valueToMatch} = where_param;
//collectionReferenceQuery = collectionReference.where(searchByField, whereFilterOp, valueToMatch)
queryCall = query(queryCall, where(searchByField, whereFilterOp, valueToMatch) )
}
if(orderBy) {
let { docField, direction} = orderBy;
//collectionReferenceQuery = collectionReference.orderBy(docField, direction)
queryCall = query(queryCall, fs_orderBy(docField, direction) )
}
if(limitResultCount) {
//collectionReferenceQuery = collectionReference.limit(limitResultCount)
queryCall = query(queryCall, limit(limitResultCount) );
}
if(this.lastDocInSortedOrder) {
//collectionReferenceQuery = collectionReference.startAt(this.lastDocInSortedOrder)
queryCall = query(queryCall, startAt(this.lastDocInSortedOrder) )
}
return queryCall
}
See the last line removed is removed when I add a new message to the collection. Whats worse is it's not consistent. I debugged this and Firestore is removing the message.
I almost feel like this is a bug in Firestore's handling of listeners
As mentioned in the comments and confirmed by you the problem you are facing is occuring due to the fact that some values of the fields that your are searching in your query changed while the listener was still active and this makes the listener think of this document as a removed one.
This is proven by the fact that the records are not being deleted from Firestore itself, but are just being excluded from the listener.
This can be fixed by creating a better querying structure, separating the old data from new data incoming from the listener, which you mentioned you've already done in the comments as well.

How to integrate this filter into my code?

I have some list of names that I take from the array using the Fetch method. Now I'm using the method of searchHandler at the click of a button, I enter the input data into the console:
https://codesandbox.io/s/jovial-lovelace-z659k
But I need to enter the input "First name", and click on the button, only a line with that name was displayed. But I don't know how to make the filter myself.
I found the solution on the internet, but unfortunately I can't integrate it into my code.Here it is:
getFilteredData() {
if (!this.state.search){
return this.state.data
}
return this.state.data.filter(item=>{
return item["firstName"].toLowerCase().includes(this.state.search.toLowerCase())
});
}
How to integrate it into my code? And what to write in the render method?
You are in the right direction there. The correct code (with comments explaining the changes) should be:
searchHandler = search => {
// This if checks if search is empty. In that case, it reset the data to print the initial list again
if (search) {
// This 'arr' variable is a copy of what you do in your Table.js
const arr = this.state.data.group && this.state.data.group.first ? this.state.data.group.first : this.state.data;
const filteredArr = arr.filter((item) => {
// Here you compare with 'search' instead of 'state.search', since you didn't updated the state to include the search term
return item["firstName"].toLowerCase().includes(search.toLowerCase())
})
// This will update your state, which also updates the table
this.setState({data: filteredArr})
} else {
// As explained, if search was empty, return everything
this.resetData();
}
};
// This is a copy of what you have in componentDidMount
async resetData() {
const response = await fetch("/data/mass.json");
const data = await response.json();
this.setState({
data
});
}
Note:
includes is not supported by all browsers, as you can see here. If you need a more reliable solution, you could use indexOf, as explained here.
Since your fetched data is an array of objects, and you basically want to filter out the objects which match the serach criteria, heres how you can write your search handler:
searchHandler = search => {
const { data } = this.state;
const filteredData = {
group: {
first: Object.values(data.group.first).filter(item => item.firstName.includes(search)),
}
}
this.setState({ data: filteredData });
};
Basically, what its doing is taking the array of objects out of dataand filter out only those objects which have the name you search for. and sets the filtered array of objects in the same structure as your original data object is and there you go!!
Also you don't have to make any changes to the render method now. Since render method is already working with the state which has data in it. and as soon as you make a search state, data will be updated and available in the render.

How to create a `context.Provider`/`context.Consumer`-like structure to pass values in a bot app?

I'm trying to pass a property, that is inside the first position of an array of objects, to another module so I can use this value later. I've tried to pass it as module(args), but it keeps reading the default value which is 0. Is there a way to do this?
I tried to implement some React.context but the Bot framework Emulator is refusing it.
/////////////////Module that ll acquire the value/////////////////////////////
getCard(bot, builder, params) {
let configValues = { ...params[0] }
bot.dialog(`${configValues.path}`, function (session) {
var msg = new builder.Message(session);
const cardItem = (obj) => {
return (new builder.HeroCard(session)
.title(`${obj.title}`)
.text(`R$ ${obj.price}`)
.images([builder.CardImage.create(session, `${obj.img}`)])
.buttons([
builder.CardAction.imBack(session, `${obj.price} Item adicionado!`, 'add to cart')
// !onClick event must add the current obj.price to
// the configValues.total(Ex: configValues.total += obj.price)!
])
)
}
msg.attachmentLayout(builder.AttachmentLayout.carousel)
msg.attachments(
eval(params.map(obj => cardItem(obj)))
);
//!in here before end the dialog is where i want to update
// the configValues.total so i can show it in the -> Checkout module
session.send(msg).endDialog()
}).triggerAction({ matches: configValues.regex });
}
}
//////////////CheckOut.Module///////////////////////////////
{...}
let configValues = { ...params[0] }
let state = {
nome: "",
endereco: "",
pagamento: "",
total: configValues.total // this is the value to be read
}
bot.dialog('/intent', [
{...},
(session, results) => {
state.pagamento = results.response
session.send(
JSON.stringify(state) // here is the place to be printed
)
{...}
]
).triggerAction({ matches: /^(finalizar|checar|encerrar|confirmar pedido|terminar)/i })
Since you solved your original problem, I'll answer the one in your comment.
Your problem is here:
cartId.map((obj, i , arr) => {
// if (!obj.total) {
// obj.total.reduce(i => i += i)
// }
const newtotal = new total
newtotal.getTotals(bot, builder, obj, arr)
})
cartId contains the totals for each of your items. When you call map on it, you're passing each item individually to getTotals, which passes each item to checkout()
The reason you can't sum all of the totals and can only sum one item's total is that you pass cartId to checkout and cartId has been changed to just a single item. Instead, there's a couple of different things you could do:
Pass the whole cartId from cartItems and use something like for (var key in cartItems) in totalConstructor() and checkoutConstructor(). This is probably the easiest, but not very memory efficient.
Use BotBuilder's State Storage to store your totals array in userData, then sum that at the end. This might be more difficult to implement, but would be a much better route to go. Here's a sample that can help you get started.

Angular 2 Observables - Empty response on action still shows data in array

I have this function that aggregates some user data from Firebase in order to build a "friend request" view. On page load, the correct number of requests show up. When I click an "Accept" button, the correct connection request gets updated which then signals to run this function again, since the user is subscribed to it. The only problem is that once all of the friend requests are accepted, the last remaining user stays in the list and won't go away, even though they have already been accepted.
Here is the function I'm using to get the requests:
getConnectionRequests(userId) {
return this._af.database
.object(`/social/user_connection_requests/${userId}`)
// Switch to the joined observable
.switchMap((connections) => {
// Delete the properties that will throw errors when requesting
// the convo keys
delete connections['$key'];
delete connections['$exists'];
// Get an array of keys from the object returned from Firebase
let connectionKeys = Object.keys(connections);
// Iterate through the connection keys and remove
// any that have already been accepted
connectionKeys = connectionKeys.filter(connectionKey => {
if(!connections[connectionKey].accepted) {
return connectionKey;
}
})
return Observable.combineLatest(
connectionKeys.map((connectionKey => {
return this._af.database.object(`/social/users/${connectionKey}`)
}))
);
});
}
And here is the relevant code in my Angular 2 view (using Ionic 2):
ionViewDidLoad() {
// Get current user (via local storage) and get their pending requests
this.storage.get('user').then(user => {
this._connections.getConnectionRequests(user.id).subscribe(requests => {
this.requests = requests;
})
})
}
I feel I'm doing something wrong with my observable and that's why this issue is happening. Can anyone shed some light on this perhaps? Thanks in advance!
I think you nailed it in your comment. If connectionKeys is an empty array calling Observable.combineLatest is not appropriate:
import 'rxjs/add/observable/of';
if (connectionKeys.length === 0) {
return Observable.of([]);
}
return connectionKeyObservable.combineLatest(
connectionKeys.map(connectionKey =>
this._af.database.object(`/social/users/${connectionKey}`)
)
);

Observe changes to a nested array doesn't return element added/changed

I am using an observeChanges on this cursor as follows. The cursor contains a nested array called "messages".
Connections.find({
status: 'connected'
}).observeChanges({
added: function (id) {
console.log(`Connected ${id}`);
},
changed: function (id, fields) {
// I expect fields to be the last array item pushed
console.log(fields['messages']);
}
});
However, this yields all the messages, rather than the ones changed or added. How can you just get the last ones pushed? Getting the last one won't work as intended because you can push multiple at a time.
Here is a fully working example on meteorpad if you want to see what I mean in action (look at console.logs)
You have to disable the observeChanges during the first step to only catch new things:
var init = true;
Connections.find({
status: 'connected'
}).observeChanges({
added: function (id) {
if(init) return;
console.log(`Connected ${id}`);
},
changed: function (id, fields) {
if(init) return;
// I expect fields to be the last array item pushed
console.log(fields['messages']);
}
});
init = false;
here is the meteorpad version:
http://meteorpad.com/pad/2NcJwxHYQNTqktMa5/Copy%20of%20Leaderboard
I've found a way to do it, it's just not using observeChanges, uses observe instead.
Connections.find({
status: 'connected'
}).observe({
changed: function (newDoc, oldDoc) {
let newMessages = _.clone(newDoc.messages);
let old = _.clone(oldDoc.messages);
while (JSON.stringify(old) !== JSON.stringify(newMessages.slice(0, old.length))) {
old.shift();
}
let last = newMessages.slice(old.length);
}
});

Categories

Resources