📱 Reels Feed
Display reels list with infinite scroll, similar to TikTok For You page.
Core Package (@xhub-chat/core)
Basic Usage
import { createClient } from '@xhub-chat/core';
const client = createClient({
baseUrl: 'https://your-server.com',
accessToken: 'your-access-token',
userId: '@user:server.com',
});
await client.startClient();
// Load initial reels
await client.paginateReels({ limit: 10 });
// Get loaded reels
const reelsStore = client.getReelsStore();
const rooms = await reelsStore.getSavedRooms();
// Extract reel data
const reels = rooms
.map(room => room.reel)
.filter(reel => reel !== undefined);
console.log('Loaded reels:', reels.length);
Pagination
// Check if more reels available
if (client.canPaginateReels()) {
// Load more reels
await client.paginateReels({ limit: 10 });
// Get updated list
const rooms = await reelsStore.getSavedRooms();
}
Refresh Feed
// Clear cache and reload from start
client.clearReelCache();
await client.paginateReels({ limit: 10 });
Reel Data Structure
interface ReelData {
id: string;
title: string;
type: string;
description: string;
owner_id: string;
content: string;
// Media files (HLS video URLs)
media: { type: string; url: string }[];
thumbnail?: { type: string; url: string };
// Metadata
tags: string[];
status: string;
participants: string[] | null;
is_allow_comment: boolean;
// Timestamps
created_at: string;
updated_at: string;
updated_by: string | null;
scheduled_at: string | null;
// Owner/Creator info
owner?: {
id: string;
email: string;
first_name: string;
last_name: string;
avatar: string;
};
// Stats
likes: number;
dislikes?: number;
views: number;
total_comments: number;
// User interactions
liked: boolean;
}
React Package (@xhub-chat/react)
Basic Usage
import { useReelsFeed } from '@xhub-chat/react';
function ReelsFeed() {
const { reels, loading, error } = useReelsFeed({ limit: 10 });
if (loading) return <div>Loading reels...</div>;
if (error) return <div>Error: {error.message}</div>;
return (
<div>
{reels.map(reel => (
<ReelCard key={reel.id} reel={reel} />
))}
</div>
);
}
function ReelCard({ reel }: { reel: ReelData }) {
const videoUrl = reel.media?.[0]?.url;
const posterUrl = reel.thumbnail?.url;
const creatorName = reel.owner
? `${reel.owner.first_name} ${reel.owner.last_name}`
: 'Unknown';
return (
<div className="reel-card">
<video src={videoUrl} poster={posterUrl} />
<h3>{reel.title}</h3>
<p>{creatorName}</p>
<div className="stats">
<span>❤️ {reel.likes}</span>
<span>👁️ {reel.views}</span>
<span>💬 {reel.total_comments}</span>
</div>
</div>
);
}
Infinite Scroll
import { useReelsFeed } from '@xhub-chat/react';
import { useEffect, useRef } from 'react';
function InfiniteReelsFeed() {
const {
reels,
loading,
fetching, // Loading more reels
hasMore,
loadMore
} = useReelsFeed({ limit: 10 });
const observerRef = useRef<IntersectionObserver>();
const lastReelRef = useRef<HTMLDivElement>(null);
useEffect(() => {
if (fetching || !hasMore) return;
observerRef.current = new IntersectionObserver(entries => {
if (entries[0].isIntersecting) {
loadMore();
}
});
if (lastReelRef.current) {
observerRef.current.observe(lastReelRef.current);
}
return () => observerRef.current?.disconnect();
}, [fetching, hasMore, loadMore]);
return (
<div className="feed">
{reels.map((reel, index) => (
<div
key={reel.id}
ref={index === reels.length - 1 ? lastReelRef : null}
>
<ReelCard reel={reel} />
</div>
))}
{fetching && <div>Loading more...</div>}
{!hasMore && <div>No more reels</div>}
</div>
);
}
Pull to Refresh
import { useReelsFeed } from '@xhub-chat/react';
function RefreshableReelsFeed() {
const {
reels,
loading,
refreshing, // Refreshing state
refresh
} = useReelsFeed({ limit: 10 });
const handleRefresh = async () => {
await refresh();
};
return (
<div>
<button
onClick={handleRefresh}
disabled={refreshing}
>
{refreshing ? '🔄 Refreshing...' : '🔄 Refresh'}
</button>
{loading && <div>Initial loading...</div>}
<div className="feed">
{reels.map(reel => (
<ReelCard key={reel.id} reel={reel} />
))}
</div>
</div>
);
}
Loading States
The useReelsFeed hook provides 3 different loading states:
function ReelsFeedWithStates() {
const {
reels,
loading, // true when initial loading
fetching, // true when loading more (pagination)
refreshing, // true when refreshing
loadMore,
refresh,
hasMore
} = useReelsFeed({ limit: 10 });
return (
<div>
{/* Initial loading spinner */}
{loading && reels.length === 0 && (
<div className="initial-loader">
<Spinner size="large" />
</div>
)}
{/* Reels list */}
<div className="reels-list">
{reels.map(reel => (
<ReelCard key={reel.id} reel={reel} />
))}
</div>
{/* Load more indicator */}
{fetching && (
<div className="load-more-indicator">
<Spinner size="small" /> Loading more...
</div>
)}
{/* Refresh indicator */}
{refreshing && (
<div className="refresh-overlay">
<Spinner /> Refreshing...
</div>
)}
{/* Load more button */}
{hasMore && !fetching && (
<button onClick={loadMore}>Load More</button>
)}
</div>
);
}
Advanced: Custom Options
function CustomReelsFeed() {
const { reels } = useReelsFeed({
limit: 20, // Load 20 reels per page
autoLoad: true, // Auto-load on mount (default)
clearCacheOnUnmount: true, // Clear cache when unmount (default)
});
return <div>{/* ... */}</div>;
}
Advanced: Manual Control
function ManualReelsFeed() {
const { reels, loadMore } = useReelsFeed({
autoLoad: false, // Don't load on mount
});
const handleLoadReels = () => {
loadMore(); // Manual trigger
};
return (
<div>
<button onClick={handleLoadReels}>Load Reels</button>
{reels.map(reel => (
<ReelCard key={reel.id} reel={reel} />
))}
</div>
);
}
API Reference
Hook: useReelsFeed
function useReelsFeed(options?: IUseReelsFeedOptions): IUseReelsFeed;
interface IUseReelsFeedOptions {
/** Number of reels to load per page (default: 10) */
limit?: number;
/** Whether to auto-load initial feed on mount (default: true) */
autoLoad?: boolean;
/** Whether to clear cache on unmount (default: true) */
clearCacheOnUnmount?: boolean;
}
interface IUseReelsFeed {
/** Array of reels loaded so far */
reels: ReelData[];
/** Whether currently loading reels (initial load) */
loading: boolean;
/** Whether currently fetching more reels (load more) */
fetching: boolean;
/** Whether currently refreshing the feed */
refreshing: boolean;
/** Error if fetch failed */
error: Error | null;
/** Load more reels (for infinite scroll) */
loadMore: () => Promise<void>;
/** Whether more reels are available to load */
hasMore: boolean;
/** Manually refresh the feed (clears and reloads from start) */
refresh: () => Promise<void>;
}
Client Methods
// Core package methods
client.paginateReels({ limit: number }): Promise<void>
client.canPaginateReels(): boolean
client.clearReelCache(): void
client.getReelsStore(): EphemeralStore | undefined
Best Practices
1. Memory Management
Reels are stored in memory (EphemeralStore), so proper management is needed:
// ✅ Good: Auto-cleanup on unmount
function ReelsFeed() {
const { reels } = useReelsFeed({
clearCacheOnUnmount: true // Default
});
return <div>{/* ... */}</div>;
}
// ❌ Bad: Keep cache forever
function ReelsFeed() {
const { reels } = useReelsFeed({
clearCacheOnUnmount: false
});
return <div>{/* ... */}</div>;
}
2. Pagination Strategy
// ✅ Good: Load reasonable batch size
const { reels } = useReelsFeed({ limit: 10 });
// ❌ Bad: Load too many at once
const { reels } = useReelsFeed({ limit: 100 });
3. Error Handling
function ReelsFeed() {
const { reels, error, refresh } = useReelsFeed();
if (error) {
return (
<div className="error">
<p>Failed to load reels: {error.message}</p>
<button onClick={refresh}>Try Again</button>
</div>
);
}
return <div>{/* ... */}</div>;
}
4. Loading States
// ✅ Good: Show appropriate loading UI
function ReelsFeed() {
const { reels, loading, fetching, refreshing } = useReelsFeed();
return (
<div>
{loading && <InitialLoader />}
{refreshing && <RefreshOverlay />}
{reels.map(reel => <ReelCard key={reel.id} reel={reel} />)}
{fetching && <LoadMoreSpinner />}
</div>
);
}
// ❌ Bad: No loading feedback
function ReelsFeed() {
const { reels } = useReelsFeed();
return <div>{reels.map(reel => <ReelCard key={reel.id} reel={reel} />)}</div>;
}
Related
- Reel Player - Play video and interact with reel
- Reel Comments - View and send comments