I'm making a simple pagination for the comments on the projects i'm working on. But i have an issue where i can keep requesting more comments and get a blank page.
My API URL for fetching comments is: {{URL}}/responses?id={{id}}&skip={{skip}}&take=10
nextComments = () => {
if (this.state.skip <= this.state.responses.total) {
this.setState((prevState) => ({
skip: prevState.skip +
}), async () => {
const responsesbyId = await getResponsesbyOrgId(this.state.orgId, this.state.skip);
this.setState({
responses: responsesbyId
})
console.log(this.state.responses);
});
}
};
I've tried setting a max, but then an another issue is that when there is e.g. 16 comments i can skip 10 then 10 more and end up with a blank page again.
Is there a smarter way to deal with this? so i round up to 6 when there is less than 10 comments left?
hope i'm clear in my question.
I'm assuming you have control over your backend based on the question, so ideally, this is something your API should make the front end aware of. The API should return the total number of results from a query, as well as the number of results per page and what page you're currently viewing (starts at 1). Then the front end can look at those values and dynamically control the pagination logic.
So for example, API response could be something like:
const res = {
results: [...],
resultCount: 16,
page: 1,
resultsPerPage: 10
};
(you'd be storing the current page in the front end state of course, but just added it to the backend response since it usually doesn't hurt to return the request params).
Then in your front end, where you're storing the current page value, the logic could be
if ((currentPage * resultsPerPage) < resultCount) { /* You can let them fetch another page */}
This would satisfy your requirements of not letting them view more pages if they shouldn't be able to, and also lets the results per page variable change in the backend without the front end having to do a thing.
Related
I have an angular app where i am querying my firebase database as below:
constructor() {
this.getData();
}
getData() {
this.projectSubscription$ = this.dataService.getAllProjects()
.pipe(
map((projects: any) =>
projects.map(sc=> ({ key: sc.key, ...sc.payload.val() }))
),
switchMap(appUsers => this.dataService.getAllAppUsers()
.pipe(
map((admins: any) =>
appUsers.map(proj =>{
const match: any = admins.find(admin => admin.key === proj.admin);
return {...proj, imgArr: this.mapObjectToArray(proj.images), adminUser: match.payload.val()}
})
)
)
)
).subscribe(res => {
this.loadingState = false;
this.projects = res.reverse();
});
}
mapObjectToArray = (obj: any) => {
const mappedDatas = [];
for (const key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
mappedDatas.push({ ...obj[key], id: key });
}
}
return mappedDatas;
};
And here is what I am querying inside dataService:
getAllProjects() {
return this.afDatabase.list('/projects/', ref=>ref.orderByChild('createdAt')).snapshotChanges();
}
getAllAppUsers() {
return this.afDatabase.list('/appUsers/', ref=>ref.orderByChild('name')).snapshotChanges();
}
The problem I am facing with this is I have 400 rows of data which I am trying to load and it is taking around 30seconds to load which is insanely high. Any idea how can I query this in a faster time?
We have no way to know whether the 30s is reasonable, as that depends on the amount of data loaded, the connection latency and bandwidth of the client, and more factors we can't know/control.
But one thing to keep in mind is that you're performing 400 queries to get the users of each individual app, which is likely not great for performance.
Things you could consider:
Pre-load all the users once, and then use that list for each project.
Duplicate the name of each user into each project, so that you don't need to join any data at all.
If you come from a background in relational databases the latter may be counterintuitive, but it is actually very common in NoSQL data modeling and is one of the reasons NoSQL databases scale so well.
I propose 3 solutions.
1. Pagination
Instead of returning all those documents on app load, limit them to just 10 and keep record of the last one. Then display the 10 (or any arbitrary base number)
Then make the UI in such a way that the user has to click next or when the user scrolls, you fetch the next set based on the previous last document's field's info.
I'm supposing you need to display all the fetched data in some table or list so having the UI paginate the data should make sense.
2. Loader
Show some loader UI on website load. Then when all the documents have fetched, you hide the loader and show the data as you want. You can use some custom stuff for loader, or choose from any of the abundant libraries out there, or use mat-progress-spinner from Angular Material
3. onCall Cloud Function
What if you try getting them through an onCall cloud function? It night be faster because it's just one request that the app will make and Firebase's Cloud Functions are very fast within Google's data centers.
Given that the user's network might be slow to iterate the documents but the cloud function will return all at once and that might give you what you want.
I guess you could go for this option only if you really really need to display all that data at once on website load.
... Note on cost
Fetching 400 or more documents every time a given website loads might be expensive. It'll be expensive if the website is visited very frequently by very many users. Firebase cost will increase as you are charged per document read too.
Check to see if you could optimise the data structure to avoid fetching this much.
This doesn't apply to you if this some admin dashboard or if fetching all users like this is done rarely making cost to not be high in that case.
I don't know if what I'm trying to do is possible but I've been trying for a week now and I feel like I'm so close but I just can't get there. Maybe it's not possible? Anyway, using reactjs and next js (I'm new to coding so I'll try my best to explain clearly what I'm trying to do):
I have a webapp that shows user transactions (from an api that I don't own/didn't create). I added like/ comment functionality to this app using firestore. Basically every time someone comments or likes a post their username and like/comment get's stored in my database along with the post id (which came originally from the api - which again - I didn't create). I thought it would be nice to let users know when someone likes or comments on their transaction and the way I planed to do this was like this :
First, query (my database - firestore) all posts that have a like or a comment, then set that post_ID in state
Next, I'll loop through all of those post_ID's and use them to complete a URL for an api fetch. You see, the api ends like the /sales/{saleID} .... which sale ID is the same as my post ID but the name varies because I changed it for my data base. That fetch will return a bunch of arrays , each of which show a seller/buyer.
Then I can take each buyer and seller and create a notification that says something like "hey {seller} {buyer}, someone liked your post. And I would only show this message if it's the current user and the current user matches the buyer or seller.
So part 3 I haven't done yet but I'm pretty sure I can pull off - I've done something similar. Part 1 seems to be working great! Part 2 is where I think I'm failing. I keep getting a "failed to fetch" but I think this is because some of the arrays are returning empty. Could you guys help me by reviewing my code and letting me know what you think? Like I said, I'm new, so I'm sure I wrote a lot of dumb stuff in there but this is a work in progress and a lot of stuff I did was me trying things out. Here is the code:
Also, here are some issues I've identified:
when I console.log(posts)- It returns an empty array twice, then it returns the array of postsID's but multiple times. Sometimes 3 times, sometimes 7 times. This number seems to vary though but 3 and 7 are what I keep seeing.
when I console.log(postID)- same idea
when I console.log(postData)- very similar. I get an empty array a couple of time, then I get an array for the post ID. Here is the strange thing. Ideally, this is supposed to contain an array of transaction info for each post ID that was used, but instead I get an empty array, then another, then my transaction arrays start coming in but a lot are duplicates.
-So seems to me that the issue is at the very beginning, the postID's being returned so many times.
I also found something else. If I comment everything out in the return and just put some dummy text, and then I console log postData (which is the most important item), then everything seems to work perfectly: I get an empty array a time or two then I get on array listed per post ID. So I'm thinking that the issue is that this component is failing because it's crashing before it's done rendering. So it's trying to return something that's not there yet (the postData.map....)
function UserNotifications() {
const [posts, setPosts] = useState([]);
const [postData, setPostData] = useState([]);
const postID = posts.map((post) => post.data().likePostID);
//------------ STEP 1: GET DATA FROM FIREBASE AND USE DATA ON API -------------------//
useEffect(() => {
onSnapshot(
query(collection(db, 'Feed Posts'), orderBy('timestamp', 'desc')),
(snapshot) => setPosts(snapshot.docs)
);
}, []);
useEffect(() => {
if (posts.length != 0) {
postID.map(async (likePostID) => {
const response = await fetch(
`https://proton.api.atomicassets.io/atomicmarket/v1/sales/${likePostID}`
);
const { data } = await response.json();
setPostData(data);
});
}
}, [posts]);
console.log(postData);
//----------------------------------- STEP 2: RETURN ----------------------------------//
return (
<>
{' '}
notifications coming soon
{/* {postData.map((result) => {
const { buyer, seller } = result;
if (seller || buyer) {
return <> hello {buyer}</>;
}
})} */}
</>
);
}
export default UserNotifications;
Rendering virtually always happens multiple times in React, and it is up to you to prevent issues caused by uninitialized data, which is the case here.
A simple way to do so is by adding another return, just above the return that you already have, as follows:
if (!postData)
return null;
You need to do this for all variables that can be empty (or otherwise invalid) at any time during rendering.
I'm trying to build an application that uses Server Sent Events in order to fetch and show some tweets (latest 50- 100 tweets) on UI.
Url for SSE:
https://tweet-service.herokuapp.com/stream
Problem(s):
My UI is becoming unresponsive because there is a huge data that's coming in!
How do I make sure My UI is responsive? What strategies should I usually adopt in making sure I'm handling the data?
Current Setup: (For better clarity on what I'm trying to achieve)
Currently I have a Max-Heap that has a custom comparator to show latest 50 tweets.
Everytime there's a change, I am re-rendering the page with new max-heap data.
We should not keep the EventSource open, since this will block the main thread if too many messages are sent in a short amount of time. Instead, we only should keep the event source open for as long as it takes to get 50-100 tweets. For example:
function getLatestTweets(limit) {
return new Promise((resolve, reject) => {
let items = [];
let source = new EventSource('https://tweet-service.herokuapp.com/stream');
source.onmessage = ({data}) => {
if (limit-- > 0) {
items.push(JSON.parse(data));
} else {
// resolve this promise once we have reached the specified limit
resolve(items);
source.close();
}
}
});
}
getLatestTweets(100).then(e => console.log(e))
You can then compare these tweets to previously fetched tweets to figure out which ones are new, and then update the UI accordingly. You can use setInterval to call this function periodically to fetch the latest tweets.
I have a piece of functionality in my Angular app in which I have some searchable items that exist within my Algolia database. The thing is...they aren't searchable by any string. They are only searchable via facets.
The problem is that when I run my initial .search() function with an empty string + my search filters/facets, I get a list returned and everything is all fine and dandy. HOWEVER, when I go to run the function again to refresh the list, it just comes back with the same results and actually never fires a new request, unless I change one of the filters/facets.
Is there any way to force a search query any time I want, without having to specify a "new" search criteria?
Here is my search function:
searchForAuditions() {
// Setup the filters/options
let options = {
highlightPreTag: '<span class="highlighted">',
highlightPostTag: '</span>',
hitsPerPage: 10,
};
// Add clause to make sure "is_deleted" is false
let isDeleted = ` is_deleted: "false"`;
let facets = `${isDeleted}`;
// Replace all trailing spaces and split it into an array
let facetWords = facets.replace(/\s+$/, '').split(" ");
// Remove trailing "AND"
if((facetWords[facetWords.length - 1] === "AND") || (facetWords[facetWords.length - 1] === "OR")) {
facetWords.splice(-1, 1);
facets = facetWords.join(' ');
}
if(facets) {
options['filters'] = facets;
}
this.auditionsIndex.search('', options).then(result => {
this.results = result.hits;
})
}
Thanks in advance!
From the algolia JavaScript client documentation,
To avoid performing the same API calls twice search results will be stored in a cache that will be tied to your JavaScript client and index objects. Whenever a call for a specific query (and filters) is made, we store the results in a local cache. If you ever call the exact same query again, we read the results from the cache instead of doing an API call.
This is particularly useful when your users are deleting characters
from their current query, to avoid useless API calls. Because it is
stored as a simple JavaScript object in memory, the cache is
automatically reset whenever you reload the page.
To resolve this you should use
index.clearCache() or client.clearCache()
Read more about the inbuilt cache system here.
https://github.com/algolia/algoliasearch-client-javascript#cache
I was searching for an easy and simple database for a little highscore system for a some games I'm developing in javascript.
I saw Orchestrate.io in github's student developer pack. I found a suitable drivermodule nodejs orchestrate and have integrated them.
The problem comes with querying orchestrate for my data. I have managed saving scores and querying them with db.list('collection'), but this seems to not responding with all data. It appered to me that some values are not returned.
I read about the db.search('collection','query') function. But I don't really understand how I could return all data because I don't want to query in a specific way.
My objects are as simple as follows:
{"name":"Jack","score":1337}
As I understand, one has to send a key, when putting such values to an orchestrate-collection. But I'd like to query the whole collection and get the values in a descendant order in regard to the score.
As for now I end up sorting the result on the client-side.
I hope you guys can give me some hints for a query that can sort for specific values!
You have the option to use a SearchBuilder
db.newSearchBuilder() //Build a search object
.collection('collection') //Set the collection to be searched
.sort(score, 'desc') //Set the order of the results
.query("*") //Empty search
.then(function (res) { //Callback function for results
//Do something with the results
})
Source
By default, .list uses a pagination limit of 10. You can either increase that, e.g.:
db.list('collection', { limit: 100 })
Or use .links, .links.next (from the docs):
db.list('collection', { limit: 10 })
.then(function (page1) {
// Got First Page
if (page1.links && page1.links.next) {
page1.links.next.get().then(function (page2) {
// Got Second Page
})
}
})