Abdulhadi Bakr
Posted on January 20, 2021
Why you should use infinite scroll in your App?
When you have a lot of data, that should be shown in any page in your app, it's not efficient and not recommended to fetch them all at once. This way will make the app slow and will provide a bad user experience.
So the solution here is to use 'Infinite Scroll' to fetch them as batches.
How does infinite scroll work in firebase?
First off, fetch first 10 documents for example, then store key of last fetched document (key could be any field in document), then use this key to execute new query to fetch next 10 documents starting after last fetched document.
In firebase you can apply pagination by using 3 methods:
-
orderBy():
specify the sort order for your documents by using any filed in document. -
stratAfter():
to define the start point for a query. After any document should the next batch start? -
limit():
limit the number of documents retrieved.
The queries will looks like:
const firstBatch = db.collection('posts')
.orderBy('createdAt')
.limit(5)
.get();
const nextBatch = db.collection('posts')
.orderBy('createdAt')
.startAfter(last_doc_in_firstBatch.createdAt)
.limit(5)
.get();
⚠️ Note: the field that will be used in orderBy() and startAfter() should be the same field ex. 'createdAt'
Let's start coding.. 👨💻🏃♂️
Database (firestore)
Folders and files structure
Project structure plays an important role in project maintenance, and provides the ability to scale. so our structure will looks like:
services
contains the files which will execute the queries on database (fetch posts).
utils
contains utility functions that will be used repeatedly in the project (firebase reference).
firebase.js
Contains firebase config and reference to database, that will be used in Post.js
to execute the queries.
import firebase from "firebase/app";
import "firebase/firestore";
const firebaseConfig = {
apiKey: "AIzaSyBL1gveQXduGppv-llH_x_w4afHkFU_UeU",
authDomain: "fir-38a4a.firebaseapp.com",
projectId: "fir-38a4a",
storageBucket: "fir-38a4a.appspot.com",
messagingSenderId: "824375282175",
appId: "1:824375282175:web:353e6759f7d8378fe33fca"
};
firebase.initializeApp(firebaseConfig);
const db = firebase.firestore();
export default db;
Post.js
Contains the queries, that will fetch the posts from database.
import db from "../utils/firebase";
export default {
/**
* this function will be fired when you first time run the app,
* and it will fetch first 5 posts, here I retrieve them in descending order, until the last added post appears first.
*/
postsFirstBatch: async function () {
try {
const data = await db
.collection("posts")
.orderBy("createdAt", "desc")
.limit(5)
.get();
let posts = [];
let lastKey = "";
data.forEach((doc) => {
posts.push({
postId: doc.id,
postContent: doc.data().postContent
});
lastKey = doc.data().createdAt;
});
return { posts, lastKey };
} catch (e) {
console.log(e);
}
},
/**
* this function will be fired each time the user click on 'More Posts' button,
* it receive key of last post in previous batch, then fetch next 5 posts
* starting after last fetched post.
*/
postsNextBatch: async (key) => {
try {
const data = await db
.collection("posts")
.orderBy("createdAt", "desc")
.startAfter(key)
.limit(5)
.get();
let posts = [];
let lastKey = "";
data.forEach((doc) => {
posts.push({
postId: doc.id,
postContent: doc.data().postContent
});
lastKey = doc.data().createdAt;
});
return { posts, lastKey };
} catch (e) {
console.log(e);
}
}
};
App.js
First off, import 'Post.js' file.
import Post from "./services/Post";
Then init local state using 'useState' hook.
const [posts, setPosts] = useState([]);
const [lastKey, setLastKey] = useState("");
const [nextPosts_loading, setNextPostsLoading] = useState(false);
Then in 'useEffect' fetch first batch of posts and lastKey, and set them in local state, when you first time run the app, first 5 posts will be shown.
useEffect(() => {
// first 5 posts
Post.postsFirstBatch()
.then((res) => {
setPosts(res.posts);
setLastKey(res.lastKey);
})
.catch((err) => {
console.log(err);
});
}, []);
Then create a function to fetch next batch of posts, this function receive 'lastKey' as argument. It will be fired when user click on 'More Posts' button.
const fetchMorePosts = (key) => {
if (key.length > 0) {
setNextPostsLoading(true);
Post.postsNextBatch(key)
.then((res) => {
setLastKey(res.lastKey);
// add new posts to old posts
setPosts(posts.concat(res.posts));
setNextPostsLoading(false);
})
.catch((err) => {
console.log(err);
setNextPostsLoading(false);
});
}
};
Then create a constant to store all our posts
const allPosts = (
<div>
{posts.map((post) => {
return (
<div key={post.postId}>
<p>{post.postContent}</p>
</div>
);
})}
</div>
);
Last step, UI
return (
<div className="App">
<h2>Infinite scroll in Firebase(firestore) and React.js</h2>
<div>{allPosts}</div>
<div style={{ textAlign: "center" }}>
{nextPosts_loading ? (
<p>Loading..</p>
) : lastKey.length > 0 ? (
<button onClick={() => fetchMorePosts(lastKey)}>More Posts</button>
) : (
<span>You are up to date!</span>
)}
</div>
</div>
);
⚠️ Note: when there are no more posts 'lastKey' will be set to '', therefore we check its length here, until we can detect that there are no more posts.
Live demo 🎊
Get the full code of this article. 📁
If you would like to check this feature in a real project, look here I applied it to home page posts in my last social network project (in react.js).
I hope that you found this article useful, and you enjoyed the article 😊
Bye 👋
Posted on January 20, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.