BigBlocks Docs

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
No posts foundNo posts to show.Be the first to share something on the Bitcoin blockchain!

Installation

npx bigblocks add social-feed

Import

import { SocialFeed } from 'bigblocks';

This component extends the ScrollArea primitive and inherits all its props.

Props

PropTypeRequiredDefaultDescription
dataPostsResponseNo-Pre-loaded posts data
loadingbooleanNofalseLoading state indicator
hasMorebooleanNotrueWhether more posts are available
showCreatePostbooleanNofalseShow post creation button
showFiltersbooleanNofalseShow feed filter controls
feedType'timeline' | 'following' | 'trending' | 'user'No'timeline'Type of feed to display
userIdstringNo-User ID for user-specific feeds
onLoadMore() => voidNo-Callback for loading more posts
onUserClick(identity: BapIdentity) => voidNo-User profile click handler
onRefresh() => voidNo-Pull-to-refresh callback
classNamestringNo-Additional CSS classes
maxHeightstring | numberNo-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 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
  • 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

  1. Implement Error Boundaries: Handle component crashes gracefully
  2. Optimize Images: Use compressed images and lazy loading
  3. Cache Strategically: Cache feed data but keep it fresh
  4. Handle Offline: Provide offline indicators and cached content
  5. 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

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