BigBlocks Docs

useSocial

Comprehensive hook combining all social operations (posts, likes, follows, friends) into a single interface

useSocial

A powerful hook that combines all social operations into a single, unified interface. It provides methods for creating posts, managing likes, following users, and handling friend relationships on the Bitcoin blockchain.

View Hook Demo →

Installation

npx bigblocks add use-social

Import

import { useSocial } from 'bigblocks';

Return Value

The hook returns an object with the following properties:

Post Operations

MethodTypeDescription
createPost(data: PostMutationData) => voidCreate a new post
createPostAsync(data: PostMutationData) => Promise<BroadcastResult>Async version returning promise

Follow Operations

MethodTypeDescription
followUser(data: FollowMutationData) => voidFollow a user
followUserAsync(data: FollowMutationData) => Promise<BroadcastResult>Async version
unfollowUser(data: FollowMutationData) => voidUnfollow a user
unfollowUserAsync(data: FollowMutationData) => Promise<BroadcastResult>Async version

Like Operations

MethodTypeDescription
likePost(data: LikeMutationData) => voidLike a post
likePostAsync(data: LikeMutationData) => Promise<BroadcastResult>Async version
unlikePost(data: LikeMutationData) => voidUnlike a post
unlikePostAsync(data: LikeMutationData) => Promise<BroadcastResult>Async version

Friend Operations

MethodTypeDescription
acceptFriendRequest(idKey: string) => voidAccept a friend request
declineFriendRequest(idKey: string) => voidDecline a friend request

State Management

PropertyTypeDescription
isLoadingbooleanAny operation is loading
isErrorbooleanAny operation has error
isSuccessbooleanLast operation succeeded
hasErrorbooleanBoolean error check
errorSocialError | nullError object if any
reset() => voidReset all operation states

Data

PropertyTypeDescription
friendsFriend[]Array of friend objects
friendRequestsBapIdentity[]Array of pending friend requests

Type Definitions

PostMutationData

interface PostMutationData {
  content: string;
  contentType?: 'text/plain' | 'text/markdown';
  app?: string;
}

FollowMutationData

interface FollowMutationData {
  idKey: string; // BAP identity key
}

LikeMutationData

interface LikeMutationData {
  txid: string;
  emoji?: string; // Default: '👍'
}

BroadcastResult

interface BroadcastResult {
  success: boolean;
  txid?: string;
  rawTx?: string;
  error?: string;
  fee?: number;
}

SocialError

interface SocialError {
  code: SocialErrorCode;
  message: string;
  details?: Error | Record<string, unknown>;
}

type SocialErrorCode = 
  | 'TRANSACTION_FAILED'
  | 'INVALID_CONTENT'
  | 'USER_NOT_FOUND'
  | 'INSUFFICIENT_FUNDS'
  | 'NETWORK_ERROR'
  | 'BROADCAST_FAILED'
  | 'ENCRYPTION_FAILED'
  | 'INVALID_BAP_ID'
  | 'RATE_LIMITED'
  | 'CONTENT_TOO_LONG'
  | 'UNAUTHORIZED'
  | 'UNKNOWN_ERROR';

Basic Usage

import { useSocial } from 'bigblocks';

function SocialDashboard() {
  const {
    createPost,
    likePost,
    followUser,
    friends,
    isLoading,
    error
  } = useSocial();

  const handlePost = () => {
    createPost({
      content: "Hello Bitcoin world!",
      contentType: 'text/plain'
    });
  };

  return (
    <div>
      <button 
        onClick={handlePost} 
        disabled={isLoading}
      >
        Create Post
      </button>
      
      {error && <div>Error: {error.message}</div>}
      
      <div>Friends: {friends.length}</div>
    </div>
  );
}

Advanced Usage

Complete Social Implementation

import { useSocial } from 'bigblocks';

function SocialHub() {
  const {
    createPostAsync,
    likePost,
    unlikePost,
    followUserAsync,
    unfollowUserAsync,
    acceptFriendRequest,
    declineFriendRequest,
    friends,
    friendRequests,
    isLoading,
    error,
    reset
  } = useSocial();

  const [postContent, setPostContent] = useState('');
  const [followingStatus, setFollowingStatus] = useState<Map<string, boolean>>(new Map());

  // Handle post creation with async/await
  const handleCreatePost = async () => {
    try {
      const result = await createPostAsync({
        content: postContent,
        contentType: 'text/markdown',
        app: 'my-social-app'
      });
      
      if (result.success) {
        console.log('Post created:', result.txid);
        setPostContent('');
        // Show success notification
      }
    } catch (err) {
      console.error('Failed to create post:', err);
    }
  };

  // Toggle follow/unfollow
  const toggleFollow = async (idKey: string) => {
    const isFollowing = followingStatus.get(idKey);
    
    try {
      if (isFollowing) {
        await unfollowUserAsync({ idKey });
        setFollowingStatus(prev => new Map(prev).set(idKey, false));
      } else {
        await followUserAsync({ idKey });
        setFollowingStatus(prev => new Map(prev).set(idKey, true));
      }
    } catch (err) {
      console.error('Follow operation failed:', err);
    }
  };

  // Handle friend requests
  const handleFriendRequest = (requestId: string, accept: boolean) => {
    if (accept) {
      acceptFriendRequest(requestId);
    } else {
      declineFriendRequest(requestId);
    }
  };

  // Auto-clear errors after 5 seconds
  useEffect(() => {
    if (error) {
      const timer = setTimeout(reset, 5000);
      return () => clearTimeout(timer);
    }
  }, [error, reset]);

  return (
    <div className="space-y-4">
      {/* Post Creation */}
      <div>
        <textarea
          value={postContent}
          onChange={(e) => setPostContent(e.target.value)}
          placeholder="What's on your mind?"
          maxLength={217}
          disabled={isLoading}
        />
        <button
          onClick={handleCreatePost}
          disabled={isLoading || !postContent.trim()}
        >
          {isLoading ? 'Posting...' : 'Post'}
        </button>
      </div>

      {/* Friend Requests */}
      {friendRequests.length > 0 && (
        <div>
          <h3>Friend Requests ({friendRequests.length})</h3>
          {friendRequests.map(request => (
            <div key={request.idKey}>
              <span>{request.alternateName || request.idKey}</span>
              <button
                onClick={() => handleFriendRequest(request.idKey, true)}
                disabled={isLoading}
              >
                Accept
              </button>
              <button
                onClick={() => handleFriendRequest(request.idKey, false)}
                disabled={isLoading}
              >
                Decline
              </button>
            </div>
          ))}
        </div>
      )}

      {/* Friends List */}
      <div>
        <h3>Friends ({friends.length})</h3>
        {friends.map(friend => (
          <div key={friend.idKey}>
            {friend.alternateName || friend.idKey}
          </div>
        ))}
      </div>

      {/* Error Display */}
      {error && (
        <div className="error-banner">
          {error.code}: {error.message}
          <button onClick={reset}>Dismiss</button>
        </div>
      )}
    </div>
  );
}

Common Patterns

Social Feed with Actions

function SocialFeed({ posts }) {
  const { likePost, unlikePost, followUser } = useSocial();
  const [likedPosts, setLikedPosts] = useState<Set<string>>(new Set());

  const handleLike = (txid: string) => {
    if (likedPosts.has(txid)) {
      unlikePost({ txid });
      setLikedPosts(prev => {
        const next = new Set(prev);
        next.delete(txid);
        return next;
      });
    } else {
      likePost({ txid, emoji: '❤️' });
      setLikedPosts(prev => new Set(prev).add(txid));
    }
  };

  return (
    <div>
      {posts.map(post => (
        <article key={post.txid}>
          <header>
            <span>{post.author.name}</span>
            <button
              onClick={() => followUser({ idKey: post.author.idKey })}
            >
              Follow
            </button>
          </header>
          
          <p>{post.content}</p>
          
          <footer>
            <button
              onClick={() => handleLike(post.txid)}
              className={likedPosts.has(post.txid) ? 'liked' : ''}
            >
              {likedPosts.has(post.txid) ? '❤️' : '🤍'} Like
            </button>
          </footer>
        </article>
      ))}
    </div>
  );
}

Error Handling with Retry

function RobustSocialActions() {
  const { createPostAsync, error, reset } = useSocial();
  const [retryCount, setRetryCount] = useState(0);
  const maxRetries = 3;

  const createPostWithRetry = async (content: string) => {
    try {
      await createPostAsync({ content });
      setRetryCount(0); // Reset on success
    } catch (err) {
      if (retryCount < maxRetries) {
        setRetryCount(prev => prev + 1);
        // Exponential backoff
        setTimeout(() => {
          createPostWithRetry(content);
        }, Math.pow(2, retryCount) * 1000);
      }
    }
  };

  return (
    <div>
      {error && (
        <div>
          Error: {error.message}
          {retryCount > 0 && ` (Retry ${retryCount}/${maxRetries})`}
        </div>
      )}
      {/* Your UI here */}
    </div>
  );
}

Optimistic UI Updates

function OptimisticSocialFeed() {
  const { likePostAsync } = useSocial();
  const [optimisticLikes, setOptimisticLikes] = useState<Map<string, boolean>>(new Map());

  const handleLike = async (txid: string) => {
    // Optimistically update UI
    setOptimisticLikes(prev => new Map(prev).set(txid, true));
    
    try {
      await likePostAsync({ txid });
      // Success - keep the optimistic update
    } catch (err) {
      // Revert on failure
      setOptimisticLikes(prev => {
        const next = new Map(prev);
        next.delete(txid);
        return next;
      });
    }
  };

  return (
    <div>
      {/* Show likes including optimistic updates */}
    </div>
  );
}

Authentication Requirements

The useSocial hook requires proper authentication context:

import { 
  BitcoinAuthProvider, 
  BitcoinQueryProvider,
  BitcoinThemeProvider 
} from 'bigblocks';

function App() {
  return (
    <BitcoinQueryProvider>
      <BitcoinAuthProvider config={{ apiUrl: '/api' }}>
        <BitcoinThemeProvider>
          <YourComponent />
        </BitcoinThemeProvider>
      </BitcoinAuthProvider>
    </BitcoinQueryProvider>
  );
}

API Integration

Required Endpoints

The hook expects the following API endpoints:

  • POST /api/social/post - Create posts
  • POST /api/social/like - Like posts
  • POST /api/social/unlike - Unlike posts
  • POST /api/social/follow - Follow users
  • POST /api/social/unfollow - Unfollow users
  • POST /api/social/friend/accept - Accept friend requests
  • POST /api/social/friend/decline - Decline friend requests
  • GET /api/social/friends - Get friends list

Bitcoin Transaction Flow

  1. User initiates social action
  2. Hook creates and signs Bitcoin transaction
  3. Transaction broadcasted to BSV network
  4. API returns transaction ID
  5. UI updates with result

Troubleshooting

INSUFFICIENT_FUNDS Error

The user's wallet doesn't have enough BSV for the transaction fee:

if (error?.code === 'INSUFFICIENT_FUNDS') {
  return <div>Please add funds to perform social actions</div>;
}

RATE_LIMITED Error

Too many requests in a short time:

if (error?.code === 'RATE_LIMITED') {
  return <div>Please wait before performing more actions</div>;
}

CONTENT_TOO_LONG Error

Posts are limited to 217 characters on-chain:

const MAX_POST_LENGTH = 217;

if (postContent.length > MAX_POST_LENGTH) {
  return <div>Post is too long ({postContent.length}/{MAX_POST_LENGTH})</div>;
}

Network Errors

Handle network connectivity issues:

if (error?.code === 'NETWORK_ERROR') {
  return (
    <div>
      Network error. Check your connection.
      <button onClick={reset}>Retry</button>
    </div>
  );
}

Best Practices

  1. Error Boundaries: Wrap social components in error boundaries
  2. Loading States: Always show loading indicators during operations
  3. Optimistic Updates: Update UI immediately for better UX
  4. Batch Operations: Use async methods for multiple operations
  5. Content Validation: Validate content length and format before posting
  6. Rate Limiting: Implement client-side rate limiting
  7. State Persistence: Consider persisting social state across sessions

API Reference

Hook Configuration

While useSocial doesn't accept direct configuration, it uses the BitcoinAuthProvider config:

<BitcoinAuthProvider config={{
  apiUrl: '/api',
  network: 'mainnet', // or 'testnet'
  broadcaster: 'https://api.whatsonchain.com/v1/bsv/main/tx/raw'
}}>

Social Protocol Standards

The hook implements standard Bitcoin social protocols:

  • B Protocol: For content storage
  • MAP Protocol: For metadata
  • BAP Protocol: For identity
  • AIP Protocol: For authentication signatures