Infinite scroll in Entity Lists

Loading

Implementing infinite scroll in Entity Lists (like in model-driven PowerApps, SharePoint lists, or custom web apps) is a popular technique to improve performance and user experience by loading data in chunks as the user scrolls down — instead of loading all data at once.

Below is a step-by-step guide (around 1000 words) that explains how to implement infinite scroll for entity lists, along with best practices and technical implementations.


What is Infinite Scroll?

Infinite Scroll is a design pattern where new data is fetched and appended to a list dynamically when the user scrolls to the bottom. It replaces traditional pagination and creates a seamless browsing experience.

Common Use-Cases:

  • Product listings (e.g., Amazon, Flipkart)
  • Social media feeds (e.g., Facebook, Twitter)
  • Activity logs or CRM records
  • Search result pages

Step-by-Step Implementation for Infinite Scroll in Entity Lists

Let’s assume you are building a custom web application using React with REST API for a back-end that delivers paginated entity data (like from SQL Server, SharePoint, or Dataverse).


Step 1: Design Backend to Support Paging

Your API must return data in pages, along with metadata like nextPageToken, page, or hasMore.

Sample API Response:

{
"data": [
{ "id": 1, "name": "Record 1" },
{ "id": 2, "name": "Record 2" }
],
"hasMore": true,
"page": 1
}

Node.js with Express Example:

app.get("/api/entityList", async (req, res) => {
const page = parseInt(req.query.page) || 1;
const pageSize = 20;

const records = await db.Entity.find()
.skip((page - 1) * pageSize)
.limit(pageSize);

const total = await db.Entity.countDocuments();

res.json({
data: records,
hasMore: page * pageSize < total,
page
});
});

Step 2: Create the Front-End List Component

Use React or another front-end framework to display your list.

const [data, setData] = useState([]);
const [page, setPage] = useState(1);
const [hasMore, setHasMore] = useState(true);
const [loading, setLoading] = useState(false);

Use useEffect to load initial data:

useEffect(() => {
loadMoreData();
}, []);

Step 3: Load More Data Function

Define a function to load more data from the server.

const loadMoreData = async () => {
if (loading || !hasMore) return;

setLoading(true);
const res = await fetch(`/api/entityList?page=${page}`);
const result = await res.json();

setData(prev => [...prev, ...result.data]);
setHasMore(result.hasMore);
setPage(prev => prev + 1);
setLoading(false);
};

Step 4: Detect Scroll Event

Detect when the user reaches the bottom of the list.

Option A: Window Scroll Listener

useEffect(() => {
const handleScroll = () => {
if (
window.innerHeight + document.documentElement.scrollTop
>= document.documentElement.offsetHeight - 50
) {
loadMoreData();
}
};

window.addEventListener("scroll", handleScroll);
return () => window.removeEventListener("scroll", handleScroll);
}, [page, hasMore]);

Option B: List Container Scroll (for div containers):

jsCopyEdit<div onScroll={handleScroll} style={{ height: '600px', overflowY: 'scroll' }}>
  {data.map(item => <div key={item.id}>{item.name}</div>)}
  {loading && <p>Loading more...</p>}
</div>

Step 5: Add Loading Indicator

Let the user know data is being fetched:

{loading && <div>Loading...</div>}
{!hasMore && <div>No more records</div>}

Step 6: Optimize with Debounce or Intersection Observer

Use lodash.debounce or the modern IntersectionObserver API for better performance.

IntersectionObserver Example:

const observer = useRef();

const lastItemRef = useCallback(node => {
if (loading) return;

if (observer.current) observer.current.disconnect();
observer.current = new IntersectionObserver(entries => {
if (entries[0].isIntersecting && hasMore) {
loadMoreData();
}
});

if (node) observer.current.observe(node);
}, [loading, hasMore]);

Use the ref in the last item:

{data.map((item, index) => {
if (index === data.length - 1) {
return <div ref={lastItemRef} key={item.id}>{item.name}</div>;
} else {
return <div key={item.id}>{item.name}</div>;
}
})}

Step 7: Handling Empty States and Errors

Include fallback messages:

{!loading && data.length === 0 && <p>No records found</p>}
{error && <p>Error loading data</p>}

Use try/catch inside loadMoreData() to handle API errors gracefully.


Step 8: Optional Enhancements

  1. Search + Infinite Scroll: Reset data and call API with search parameters on input change.
  2. Filters + Sorting: Combine filtering with pagination by passing query params.
  3. Caching: Cache already loaded data in Redux or localStorage to reduce load.
  4. Skeleton Loaders: Use placeholders instead of “Loading…” text for smoother UI.

Best Practices

  • Always show a loader when fetching more data
  • Implement throttling to prevent excessive calls
  • Make sure scrolling is accessible and keyboard friendly
  • Avoid memory leaks — clear event listeners or observers on unmount
  • Consider server-side sorting/filtering to maintain performance
  • Maintain scroll position if navigating back to the list

Infinite Scroll vs Pagination

FeatureInfinite ScrollPagination
UX FlowSeamlessControlled and predictable
PerformanceBetter for short sessionsBetter for large datasets
BookmarkingDifficultEasy
SEOWeak unless SSR is usedStrong SEO
Use CaseFeeds, logs, live dataReports, search results, admin UIs

Platforms That Support Infinite Scroll in Entity Lists

  1. PowerApps (Canvas): Using collections + scrollable gallery + timer or scroll position logic
  2. SharePoint: Requires custom SPFx or PowerApps integration
  3. React/Angular Apps: Using REST APIs with list components
  4. Dataverse (Model-driven apps): Infinite scroll is not natively supported, but paging can mimic it

Leave a Reply

Your email address will not be published. Required fields are marked *