SocialFeed
Display a chronological feed of on-chain social posts with infinite scroll, real-time updates, and filtering
SocialFeed
BitcoinAuthProvider
socialfeedscontent
SocialFeed combines the Radix ScrollArea primitive with Bitcoin social protocol data, providing an infinite-scrolling feed of on-chain posts with real-time updates, filtering, and interactive engagement features.
Timeline
0 posts
Installation
npx bigblocks add social-feed
Import
import { SocialFeed } from 'bigblocks';
This component extends the ScrollArea primitive and inherits all its props.
Props
Prop | Type | Required | Default | Description |
---|---|---|---|---|
data | PostsResponse | No | - | Pre-loaded posts data |
loading | boolean | No | false | Loading state indicator |
hasMore | boolean | No | true | Whether more posts are available |
showCreatePost | boolean | No | false | Show post creation button |
showFilters | boolean | No | false | Show feed filter controls |
feedType | 'timeline' | 'following' | 'trending' | 'user' | No | 'timeline' | Type of feed to display |
userId | string | No | - | User ID for user-specific feeds |
onLoadMore | () => void | No | - | Callback for loading more posts |
onUserClick | (identity: BapIdentity) => void | No | - | User profile click handler |
onRefresh | () => void | No | - | Pull-to-refresh callback |
className | string | No | - | Additional CSS classes |
maxHeight | string | number | No | - | Maximum height with scroll |
Basic Usage
import { SocialFeed } from 'bigblocks';
export default function SocialApp() {
const handleLoadMore = () => {
// Load more posts
console.log('Loading more posts...');
};
return (
<SocialFeed
feedType="timeline"
onLoadMore={handleLoadMore}
showCreatePost={true}
/>
);
}
Feed Types
Timeline Feed
Global public feed of all posts:
import { SocialFeed } from 'bigblocks';
export default function GlobalTimeline() {
return (
<SocialFeed
feedType="timeline"
showCreatePost={true}
showFilters={true}
onLoadMore={() => {
// Load more global posts
}}
/>
);
}
Following Feed
Posts from users you follow:
import { SocialFeed } from 'bigblocks';
export default function FollowingFeed() {
return (
<SocialFeed
feedType="following"
showCreatePost={true}
onLoadMore={() => {
// Load more following posts
}}
onRefresh={() => {
// Refresh following feed
}}
/>
);
}
Trending Feed
Trending posts based on engagement:
import { SocialFeed } from 'bigblocks';
export default function TrendingFeed() {
return (
<SocialFeed
feedType="trending"
showFilters={true}
onLoadMore={() => {
// Load more trending posts
}}
/>
);
}
User Profile Feed
Posts from a specific user:
import { SocialFeed } from 'bigblocks';
export default function UserProfile({ userId }) {
return (
<SocialFeed
feedType="user"
userId={userId}
onLoadMore={() => {
// Load more user posts
}}
onUserClick={(identity) => {
// Navigate to user profile
router.push(`/profile/${identity.idKey}`);
}}
/>
);
}
Advanced Usage
With Pre-loaded Data
import { SocialFeed } from 'bigblocks';
import { useEffect, useState } from 'react';
export default function PreloadedFeed() {
const [postsData, setPostsData] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
const loadInitialPosts = async () => {
try {
const response = await fetch('/api/social/posts');
const data = await response.json();
setPostsData(data);
} catch (error) {
console.error('Failed to load posts:', error);
} finally {
setLoading(false);
}
};
loadInitialPosts();
}, []);
return (
<SocialFeed
data={postsData}
loading={loading}
hasMore={postsData?.pagination?.hasMore}
showCreatePost={true}
onLoadMore={() => {
// Load next page
loadMorePosts(postsData.pagination.nextOffset);
}}
/>
);
}
With Real-time Updates
import { SocialFeed } from 'bigblocks';
import { useEffect, useState } from 'react';
export default function LiveFeed() {
const [postsData, setPostsData] = useState(null);
useEffect(() => {
// Subscribe to real-time updates
const eventSource = new EventSource('/api/social/feed/stream');
eventSource.onmessage = (event) => {
const update = JSON.parse(event.data);
if (update.type === 'new_post') {
setPostsData(prev => ({
...prev,
posts: [update.post, ...prev.posts]
}));
}
if (update.type === 'post_updated') {
setPostsData(prev => ({
...prev,
posts: prev.posts.map(post =>
post.id === update.postId
? { ...post, ...update.updates }
: post
)
}));
}
};
return () => eventSource.close();
}, []);
return (
<SocialFeed
data={postsData}
feedType="timeline"
showCreatePost={true}
onRefresh={async () => {
// Refresh feed data
const fresh = await fetchLatestPosts();
setPostsData(fresh);
}}
/>
);
}
With Infinite Scroll
import { SocialFeed } from 'bigblocks';
import { useState, useCallback } from 'react';
export default function InfiniteScrollFeed() {
const [posts, setPosts] = useState([]);
const [loading, setLoading] = useState(false);
const [hasMore, setHasMore] = useState(true);
const [offset, setOffset] = useState(0);
const loadMorePosts = useCallback(async () => {
if (loading) return;
setLoading(true);
try {
const response = await fetch(`/api/social/posts?offset=${offset}&limit=20`);
const data = await response.json();
setPosts(prev => [...prev, ...data.posts]);
setOffset(prev => prev + data.posts.length);
setHasMore(data.pagination.hasMore);
} catch (error) {
console.error('Failed to load more posts:', error);
} finally {
setLoading(false);
}
}, [offset, loading]);
return (
<SocialFeed
data={{ posts, pagination: { hasMore } }}
loading={loading}
hasMore={hasMore}
onLoadMore={loadMorePosts}
showCreatePost={true}
/>
);
}
With Filtering
import { SocialFeed } from 'bigblocks';
import { useState } from 'react';
export default function FilteredFeed() {
const [activeFilters, setActiveFilters] = useState([]);
const [postsData, setPostsData] = useState(null);
const handleFilterChange = async (filters) => {
setActiveFilters(filters);
// Apply filters to feed
const params = new URLSearchParams({
...filters.reduce((acc, filter) => ({
...acc,
[filter.type]: filter.value
}), {})
});
const response = await fetch(`/api/social/posts?${params}`);
const data = await response.json();
setPostsData(data);
};
return (
<div>
<SocialFeed
data={postsData}
showFilters={true}
feedType="timeline"
onLoadMore={() => {
// Load more filtered posts
}}
/>
</div>
);
}
Constrained Height with Scroll
import { SocialFeed } from 'bigblocks';
export default function ScrollableFeed() {
return (
<div className="h-screen flex flex-col">
<header className="flex-shrink-0">
<h1>Social Feed</h1>
</header>
<div className="flex-1 overflow-hidden">
<SocialFeed
feedType="timeline"
maxHeight="100%"
showCreatePost={true}
onLoadMore={() => {
// Load more posts
}}
/>
</div>
</div>
);
}
Common Patterns
Tab-based Feeds
import { SocialFeed } from 'bigblocks';
import { useState } from 'react';
export default function TabbedFeeds() {
const [activeTab, setActiveTab] = useState('timeline');
const tabs = [
{ id: 'timeline', label: 'Timeline', feedType: 'timeline' },
{ id: 'following', label: 'Following', feedType: 'following' },
{ id: 'trending', label: 'Trending', feedType: 'trending' }
];
return (
<div>
<div className="flex border-b mb-4">
{tabs.map(tab => (
<button
key={tab.id}
onClick={() => setActiveTab(tab.id)}
className={`px-4 py-2 ${
activeTab === tab.id
? 'border-b-2 border-blue-500 text-blue-600'
: 'text-gray-500'
}`}
>
{tab.label}
</button>
))}
</div>
{tabs.map(tab => (
<div key={tab.id} style={{ display: activeTab === tab.id ? 'block' : 'none' }}>
<SocialFeed
feedType={tab.feedType}
showCreatePost={tab.id === 'timeline'}
showFilters={tab.id === 'trending'}
onLoadMore={() => {
console.log(`Loading more ${tab.feedType} posts`);
}}
/>
</div>
))}
</div>
);
}
Profile Integration
import { SocialFeed, ProfileCard } from 'bigblocks';
export default function ProfileWithFeed({ userId }) {
return (
<div className="max-w-4xl mx-auto">
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
<div className="lg:col-span-1">
<ProfileCard userId={userId} />
</div>
<div className="lg:col-span-2">
<SocialFeed
feedType="user"
userId={userId}
onUserClick={(identity) => {
// Navigate to clicked user profile
router.push(`/profile/${identity.idKey}`);
}}
onLoadMore={() => {
// Load more user posts
}}
/>
</div>
</div>
</div>
);
}
Search Integration
import { SocialFeed } from 'bigblocks';
import { useState, useEffect } from 'react';
export default function SearchFeed() {
const [searchQuery, setSearchQuery] = useState('');
const [searchResults, setSearchResults] = useState(null);
const [loading, setLoading] = useState(false);
useEffect(() => {
const searchPosts = async () => {
if (!searchQuery.trim()) {
setSearchResults(null);
return;
}
setLoading(true);
try {
const response = await fetch(`/api/social/search?q=${encodeURIComponent(searchQuery)}`);
const data = await response.json();
setSearchResults(data);
} catch (error) {
console.error('Search failed:', error);
} finally {
setLoading(false);
}
};
const debounceTimer = setTimeout(searchPosts, 300);
return () => clearTimeout(debounceTimer);
}, [searchQuery]);
return (
<div>
<div className="mb-6">
<input
type="text"
placeholder="Search posts..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="w-full px-4 py-2 border rounded-lg"
/>
</div>
{searchQuery ? (
<SocialFeed
data={searchResults}
loading={loading}
onLoadMore={() => {
// Load more search results
}}
/>
) : (
<SocialFeed
feedType="timeline"
showCreatePost={true}
onLoadMore={() => {
// Load more timeline posts
}}
/>
)}
</div>
);
}
Error Handling
import { SocialFeed } from 'bigblocks';
import { useState, useEffect } from 'react';
export default function RobustFeed() {
const [postsData, setPostsData] = useState(null);
const [error, setError] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
const loadFeed = async () => {
try {
setError(null);
const response = await fetch('/api/social/posts');
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const data = await response.json();
setPostsData(data);
} catch (err) {
setError(err.message);
console.error('Feed load error:', err);
} finally {
setLoading(false);
}
};
loadFeed();
}, []);
if (error) {
return (
<div className="text-center py-8">
<div className="text-red-500 mb-4">
Failed to load feed: {error}
</div>
<button
onClick={() => window.location.reload()}
className="px-4 py-2 bg-blue-500 text-white rounded"
>
Retry
</button>
</div>
);
}
return (
<SocialFeed
data={postsData}
loading={loading}
showCreatePost={true}
onLoadMore={() => {
// Load more with error handling
}}
onRefresh={async () => {
try {
const fresh = await fetch('/api/social/posts');
const data = await fresh.json();
setPostsData(data);
} catch (err) {
setError(err.message);
}
}}
/>
);
}
Types
interface PostsResponse {
posts: Post[];
pagination: {
total: number;
hasMore: boolean;
nextOffset: number;
limit: number;
};
filters?: {
activeFilters: string[];
availableFilters: FilterOption[];
};
}
interface Post {
id: string;
txid: string;
content: string;
author: BapIdentity;
timestamp: number;
stats: {
likes: number;
replies: number;
reposts: number;
};
userInteraction?: {
liked: boolean;
reposted: boolean;
};
}
interface BapIdentity {
idKey: string;
name?: string;
address: string;
avatar?: string;
}
Features
- Multiple Feed Types: Timeline, following, trending, and user-specific feeds
- Infinite Scroll: Seamless loading of more content
- Real-time Updates: Live post updates via WebSocket/SSE
- Post Creation: Integrated posting functionality
- Content Filtering: Advanced filtering and search capabilities
- User Interaction: Click handlers for user profiles
- Pull-to-Refresh: Mobile-friendly refresh gesture
- Optimized Rendering: Efficient virtualization for large feeds
- Responsive Design: Mobile and desktop optimized
Feed Types Explained
Timeline
- Global feed of all public posts
- Best for: Discovery and general browsing
- Sorting: Chronological or algorithmic
Following
- Personalized feed from users you follow
- Best for: Staying updated with specific users
- Requires: Authentication and following relationships
Trending
- Popular posts based on engagement metrics
- Best for: Finding viral or important content
- Metrics: Likes, replies, reposts, and views
User
- Single user's posts and activity
- Best for: Profile pages and user exploration
- Includes: Original posts and reposts
Performance Optimizations
- Virtual scrolling for large feeds
- Image lazy loading for media content
- Debounced scroll handlers for smooth performance
- Memoized components to prevent unnecessary re-renders
- Intelligent caching of feed data
- Progressive loading of post metadata
Requirements
- Authentication: Required for following and user feeds
- Provider Context: Must be wrapped in BitcoinQueryProvider
- Network: Active internet connection for real-time features
- Bitcoin Integration: For posting and interactions
Best Practices
- Implement Error Boundaries: Handle component crashes gracefully
- Optimize Images: Use compressed images and lazy loading
- Cache Strategically: Cache feed data but keep it fresh
- Handle Offline: Provide offline indicators and cached content
- Accessibility: Support keyboard navigation and screen readers
Troubleshooting
Feed Not Loading
- Check authentication status for protected feeds
- Verify API endpoints are accessible
- Ensure proper error handling is implemented
Infinite Scroll Issues
- Verify
onLoadMore
callback is provided - Check
hasMore
property is correctly set - Ensure loading state prevents duplicate requests
Real-time Updates Not Working
- Check WebSocket/SSE connection
- Verify proper event handling
- Ensure feed type matches subscription
Related Components
- PostButton - Create posts
- PostCard - Individual post display
- LikeButton - Post interactions
- FollowButton - Follow users
API Integration
The component integrates with bSocial protocol APIs:
// Get feed posts
GET /api/social/posts?type={feedType}&limit=20&offset=0
// Search posts
GET /api/social/search?q={query}&type=posts
// Real-time updates
GET /api/social/feed/stream
WS /api/social/feed/ws
Notes for Improvement
Enhanced Implementation: The actual component is more sophisticated than the basic prompt described:
- Uses bmap-api-types for proper Bitcoin transaction data structure
- Multiple specialized feed types with different behaviors
- Real-time capabilities with WebSocket support
- Advanced filtering and search integration
- Better performance optimizations and caching
- More granular user interaction callbacks