Skip to main content

webf-infinite-scrolling

Create high-performance infinite scrolling lists with pull-to-refresh and load-more capabilities using WebFListView. Use when building feed-style UIs, product catalogs, chat messages, or any scrollable list that needs optimal performance with large datasets.

Jump to install

Source facts

Repository
openwebf/webf
Last source activity
March 29, 2026 at 15:12
Detected SKILL.md language
English
Stars
2,513
Forks
163

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

File Explorer
2 files

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
webf-infinite-scrolling
description
Create high-performance infinite scrolling lists with pull-to-refresh and load-more capabilities using WebFListView. Use when building feed-style UIs, product catalogs, chat messages, or any scrollable list that needs optimal performance with large datasets.
# WebF Infinite Scrolling > **Note**: WebF development is nearly identical to web development - you use the same tools (Vite, npm, Vitest), same frameworks (React, Vue, Svelte), and same deployment services (Vercel, Netlify). This skill covers **performance optimization for scrolling lists** - a WebF-specific pattern that provides native-level performance automatically. Build high-performance infinite scrolling lists with Flutter-optimized rendering. WebF's `WebFListView` component automatically handles performance optimizations at the Flutter level, providing smooth 60fps scrolling even with thousands of items. ## Why Use WebFListView? In browsers, long scrolling lists can cause performance issues: - DOM nodes accumulate (memory consumption) - Re-renders affect all items (slow updates) - Intersection observers needed for virtualization - Complex state management for infinite loading **WebF's solution**: `WebFListView` delegates rendering to Flutter's optimized ListView widget, which: - ✅ Automatically virtualizes (recycles) views - ✅ Maintains 60fps scrolling with thousands of items - ✅ Provides native pull-to-refresh and load-more - ✅ Zero configuration - optimization happens automatically ## Critical Structure Requirement **⚠️ IMPORTANT**: For Flutter optimization to work, each list item must be a **direct child** of `WebFListView`: ### ✅ CORRECT: Direct Children ```jsx <WebFListView> <div>Item 1</div> <div>Item 2</div> <div>Item 3</div> {/* Each item is a direct child */} </WebFListView> ``` ### ❌ WRONG: Wrapped in Container ```jsx <WebFListView> <div> {/* DON'T wrap items in a container div */} <div>Item 1</div> <div>Item 2</div> <div>Item 3</div> </div> </WebFListView> ``` **Why this matters**: Flutter's ListView requires direct children to perform view recycling. If items are wrapped in a container, Flutter sees only one child (the container) and cannot optimize individual items. ## React Setup ### Installation ```bash npm install @openwebf/react-core-ui ``` ### Basic Scrolling List ```tsx import { WebFListView } from '@openwebf/react-core-ui'; function ProductList() { const products = [ { id: 1, name: 'Product 1', price: 19.99 }, { id: 2, name: 'Product 2', price: 29.99 }, { id: 3, name: 'Product 3', price: 39.99 }, // ... hundreds or thousands of items ]; return ( <WebFListView scrollDirection="vertical" shrinkWrap={true}> {products.map(product => ( // ✅ Each item is a direct child <div key={product.id} className="product-card"> <h3>{product.name}</h3> <p>${product.price}</p> </div> ))} </WebFListView> ); } ``` ### Infinite Scrolling with Load More ```tsx import { WebFListView, WebFListViewElement } from '@openwebf/react-core-ui'; import { useRef, useState } from 'react'; function InfiniteList() { const listRef = useRef<WebFListViewElement>(null); const [items, setItems] = useState([1, 2, 3, 4, 5]); const [page, setPage] = useState(1); const handleLoadMore = async () => { try { // Simulate API call await new Promise(resolve => setTimeout(resolve, 1000)); // Fetch next page const newItems = Array.from( { length: 5 }, (_, i) => items.length + i + 1 ); setItems(prev => [...prev, ...newItems]); setPage(prev => prev + 1); // Check if there's more data const hasMore = page < 10; // Example: 10 pages max // Notify WebFListView that loading finished listRef.current?.finishLoad(hasMore ? 'success' : 'noMore'); } catch (error) { // Notify failure listRef.current?.finishLoad('fail'); } }; return ( <WebFListView ref={listRef} onLoadMore={handleLoadMore} scrollDirection="vertical" shrinkWrap={true} > {items.map(item => ( <div key={item} className="item"> Item {item} </div> ))} </WebFListView> ); } ``` ### Pull-to-Refresh ```tsx import { WebFListView, WebFListViewElement } from '@openwebf/react-core-ui'; import { useRef, useState } from 'react'; function RefreshableList() { const listRef = useRef<WebFListViewElement>(null); const [items, setItems] = useState([1, 2, 3, 4, 5]); const handleRefresh = async () => { try { // Simulate API call await new Promise(resolve => setTimeout(resolve, 1000)); // Fetch fresh data const freshItems = [1, 2, 3, 4, 5]; setItems(freshItems); // Notify WebFListView that refresh finished listRef.current?.finishRefresh('success'); } catch (error) { // Notify failure listRef.current?.finishRefresh('fail'); } }; return ( <WebFListView ref={listRef} onRefresh={handleRefresh} scrollDirection="vertical" shrinkWrap={true} > {items.map(item => ( <div key={item} className="item"> Item {item} </div> ))} </WebFListView> ); } ``` ### Combined: Pull-to-Refresh + Infinite Scrolling ```tsx import { WebFListView, WebFListViewElement } from '@openwebf/react-core-ui'; import { useRef, useState } from 'react'; function FeedList() { const listRef = useRef<WebFListViewElement>(null); const [posts, setPosts] = useState([ { id: 1, title: 'Post 1', content: 'Content 1' }, { id: 2, title: 'Post 2', content: 'Content 2' }, { id: 3, title: 'Post 3', content: 'Content 3' }, ]); const [page, setPage] = useState(1); const handleRefresh = async () => { try { // Fetch latest posts const response = await fetch('/api/posts?page=1'); const freshPosts = await response.json(); setPosts(freshPosts); setPage(1); listRef.current?.finishRefresh('success'); } catch (error) { listRef.current?.finishRefresh('fail'); } }; const handleLoadMore = async () => { try { const nextPage = page + 1; // Fetch next page const response = await fetch(`/api/posts?page=${nextPage}`); const newPosts = await response.json(); setPosts(prev => [...prev, ...newPosts]); setPage(nextPage); // Check if more data exists const hasMore = newPosts.length > 0; listRef.current?.finishLoad(hasMore ? 'success' : 'noMore'); } catch (error) { listRef.current?.finishLoad('fail'); } }; return ( <WebFListView ref={listRef} onRefresh={handleRefresh} onLoadMore={handleLoadMore} scrollDirection="vertical" shrinkWrap={true} style={{ height: '100vh' }} > {posts.map(post => ( <article key={post.id} className="post-card"> <h2>{post.title}</h2> <p>{post.content}</p> </article> ))} </WebFListView> ); } ``` ## Vue Setup ### Installation ```bash npm install @openwebf/vue-core-ui ``` ### Setup Global Types In your `src/env.d.ts` or `src/main.ts`: ```typescript import '@openwebf/vue-core-ui'; ``` ### Basic Scrolling List ```vue <template> <webf-list-view scroll-direction="vertical" :shrink-wrap="true"> <div v-for="product in products" :key="product.id" class="product-card"> <h3>{{ product.name }}</h3> <p>${{ product.price }}</p> </div> </webf-list-view> </template> <script setup lang="ts"> import { ref } from 'vue'; const products = ref([ { id: 1, name: 'Product 1', price: 19.99 }, { id: 2, name: 'Product 2', price: 29.99 }, { id: 3, name: 'Product 3', price: 39.99 }, ]); </script> ``` ### Infinite Scrolling with Load More ```vue <template> <webf-list-view ref="listRef" @loadmore="handleLoadMore" scroll-direction="vertical" :shrink-wrap="true" > <div v-for="item in items" :key="item" class="item"> Item {{ item }} </div> </webf-list-view> </template> <script setup lang="ts"> import { ref } from 'vue'; const listRef = ref<HTMLElement>(); const items = ref([1, 2, 3, 4, 5]); const page = ref(1); async function handleLoadMore() { try { // Simulate API call await new Promise(resolve => setTimeout(resolve, 1000)); // Fetch next page const newItems = Array.from( { length: 5 }, (_, i) => items.value.length + i + 1 ); items.value.push(...newItems); page.value++; // Check if there's more data const hasMore = page.value < 10; // Notify WebFListView (listRef.value as any)?.finishLoad(hasMore ? 'success' : 'noMore'); } catch (error) { (listRef.value as any)?.finishLoad('fail'); } } </script> ``` ### Pull-to-Refresh ```vue <template> <webf-list-view ref="listRef" @refresh="handleRefresh" scroll-direction="vertical" :shrink-wrap="true" > <div v-for="item in items" :key="item" class="item"> Item {{ item }} </div> </webf-list-view> </template> <script setup lang="ts"> import { ref } from 'vue'; const listRef = ref<HTMLElement>(); const items = ref([1, 2, 3, 4, 5]); async function handleRefresh() { try { // Simulate API call await new Promise(resolve => setTimeout(resolve, 1000)); // Fetch fresh data items.value = [1, 2, 3, 4, 5]; // Notify WebFListView (listRef.value as any)?.finishRefresh('success'); } catch (error) { (listRef.value as any)?.finishRefresh('fail'); } } </script> ``` ## Props and Configuration ### WebFListView Props | Prop | Type | Default | Description | |------|------|---------|-------------| | `scrollDirection` | `'vertical' \| 'horizontal'` | `'vertical'` | Scroll direction for the list | | `shrinkWrap` | `boolean` | `true` | Whether list should shrink-wrap its contents | | `onRefresh` / `@refresh` | `() => void \| Promise<void>` | - | Pull-to-refresh callback | | `onLoadMore` / `@loadmore` | `() => void \| Promise<void>` | - | Infinite scroll callback (triggered near end) | | `className` / `class` | `string` | - | CSS class names | | `style` | `object` | - | Inline styles | ### Ref Methods (React) / Element Methods (Vue) | Method | Signature | Description |
View on GitHub
This SKILL.md is very large, so SkillsMP previews the first section here. View on GitHub