POWR/docs/features/profile/follower_stats.md
DocNR 969163313a fix(auth): Improve authentication state handling and avatar display
* Add style prop to UserAvatar component for better customization
* Refactor UserAvatar to use getAvatarSeed utility for consistent avatar generation
* Fix React hook ordering issues in profile/overview.tsx to prevent crashes during auth state changes
* Add proper state initialization and cleanup during authentication transitions
* Ensure consistent fallback avatar display for unauthenticated users

These changes improve stability during login/logout operations and provide better visual continuity with Robohash avatars when profile images aren't available.
2025-04-02 21:11:25 -04:00

8.0 KiB

Follower Statistics

Last Updated: 2025-04-02
Status: Implemented
Related To: Profile Tab, NostrBand Integration

Introduction

The follower statistics feature provides real-time information about a user's Nostr followers and following counts. This document outlines the technical implementation of follower statistics, which is primarily displayed in the Profile tab.

Implementation Overview

Follower statistics are implemented through integration with the NostrBand API, which provides network-wide statistics for Nostr users, including follower counts. This integration is handled through the NostrBandService and exposed via the useProfileStats hook.

Key Components

  • NostrBandService: Core service that interfaces with the NostrBand API
  • useProfileStats Hook: React hook that provides access to follower statistics
  • ProfileFollowerStats Component: UI component for displaying follower statistics

NostrBand Service Implementation

The NostrBandService (implemented in lib/services/NostrBandService.ts) provides methods for fetching follower statistics:

// NostrBand service for fetching follower statistics
export class NostrBandService {
  private baseUrl = 'https://api.nostr.band/v0';
  
  // Fetch follower statistics for a given public key
  public async getProfileStats(pubkey: string): Promise<ProfileStats> {
    if (!pubkey) {
      return { followersCount: 0, followingCount: 0 };
    }
    
    try {
      const response = await fetch(`${this.baseUrl}/profile/${pubkey}/stats`);
      
      if (!response.ok) {
        throw new Error(`NostrBand API error: ${response.status}`);
      }
      
      const data = await response.json();
      
      return {
        followersCount: data.followers_count || 0,
        followingCount: data.following_count || 0
      };
    } catch (error) {
      console.error('Error fetching profile stats:', error);
      // Return default values on error
      return { followersCount: 0, followingCount: 0 };
    }
  }
}

// Singleton instance
export const nostrBandService = new NostrBandService();

ProfileStats Interface

The ProfileStats interface defines the structure of follower statistics:

export interface ProfileStats {
  followersCount: number;
  followingCount: number;
}

useProfileStats Hook

The useProfileStats hook (implemented in lib/hooks/useProfileStats.ts) provides a React interface for accessing follower statistics:

// Hook for accessing profile statistics
export function useProfileStats({ 
  pubkey, 
  refreshInterval = 0
}: {
  pubkey: string;
  refreshInterval?: number;
}): {
  followersCount: number;
  followingCount: number;
  isLoading: boolean;
  refresh: () => Promise<void>;
} {
  const [stats, setStats] = useState<ProfileStats>({ followersCount: 0, followingCount: 0 });
  const [isLoading, setIsLoading] = useState(true);
  
  // Fetch profile stats
  const fetchStats = useCallback(async () => {
    if (!pubkey) {
      setStats({ followersCount: 0, followingCount: 0 });
      setIsLoading(false);
      return;
    }
    
    try {
      setIsLoading(true);
      const profileStats = await nostrBandService.getProfileStats(pubkey);
      setStats(profileStats);
    } catch (error) {
      console.error('Error in useProfileStats:', error);
    } finally {
      setIsLoading(false);
    }
  }, [pubkey]);
  
  // Initial fetch and refresh interval
  useEffect(() => {
    fetchStats();
    
    if (refreshInterval > 0) {
      const interval = setInterval(fetchStats, refreshInterval);
      return () => clearInterval(interval);
    }
  }, [fetchStats, refreshInterval]);
  
  // Return stats and loading state
  return {
    ...stats,
    isLoading,
    refresh: fetchStats
  };
}

Profile Integration

The follower statistics are displayed in the Profile tab using the ProfileFollowerStats component:

// Profile follower stats component
const ProfileFollowerStats = React.memo(() => {
  return (
    <View className="flex-row mb-2">
      <TouchableOpacity className="mr-4">
        <Text>
          <Text className="font-bold">{statsLoading ? '...' : followingCount.toLocaleString()}</Text>
          <Text className="text-muted-foreground"> following</Text>
        </Text>
      </TouchableOpacity>
      
      <TouchableOpacity>
        <Text>
          <Text className="font-bold">{statsLoading ? '...' : followersCount.toLocaleString()}</Text>
          <Text className="text-muted-foreground"> followers</Text>
        </Text>
      </TouchableOpacity>
    </View>
  );
});

This component is called in the Profile tab header:

// In the Profile header
const { followersCount, followingCount, isLoading: statsLoading } = useProfileStats({ 
  pubkey: pubkey || '', 
  refreshInterval: 60000 * 15 // refresh every 15 minutes
});

// Later in the JSX
<ProfileFollowerStats />

Hook Ordering Consistency

To maintain React hook ordering consistency regardless of authentication state, the useProfileStats hook is always called, even when the user is not authenticated:

// Always call useProfileStats hook, even if isAuthenticated is false
// This ensures consistent hook ordering regardless of authentication state
const { followersCount, followingCount, isLoading: statsLoading } = useProfileStats({ 
  pubkey: pubkey || '', 
  refreshInterval: 60000 * 15
});

When not authenticated or when the pubkey is empty, the hook returns default values (zero counts).

Caching Strategy

Follower statistics are cached to reduce API calls and improve performance:

// In NostrBandService
private cache = new Map<string, { stats: ProfileStats; timestamp: number }>();
private cacheTTL = 15 * 60 * 1000; // 15 minutes

public async getProfileStats(pubkey: string): Promise<ProfileStats> {
  // Check cache first
  const cached = this.cache.get(pubkey);
  if (cached && Date.now() - cached.timestamp < this.cacheTTL) {
    return cached.stats;
  }
  
  // Fetch from API if not cached or expired
  try {
    const stats = await this.fetchProfileStats(pubkey);
    
    // Update cache
    this.cache.set(pubkey, {
      stats,
      timestamp: Date.now()
    });
    
    return stats;
  } catch (error) {
    // Error handling...
  }
}

Offline Support

The follower statistics feature gracefully handles offline states:

public async getProfileStats(pubkey: string): Promise<ProfileStats> {
  // Check connectivity
  if (!this.connectivityService.isOnline()) {
    // Return cached data if available
    const cached = this.cache.get(pubkey);
    if (cached) {
      return cached.stats;
    }
    
    // Return default values if no cached data
    return { followersCount: 0, followingCount: 0 };
  }
  
  // Proceed with normal API call if online
  // ...
}

Performance Considerations

To ensure good performance, several optimizations are implemented:

  1. Memoization: The ProfileFollowerStats component is memoized to prevent unnecessary re-renders
  2. Refresh Interval: Stats are only refreshed at specific intervals (default: 15 minutes)
  3. Caching: Follower statistics are cached to reduce API calls
  4. Lazy Loading: Stats are loaded after the profile is rendered to prioritize UI responsiveness

Future Enhancements

Future enhancements to follower statistics include:

  1. Follower List: Display a list of followers/following with profile information
  2. Interaction Analytics: Show interaction statistics with followers
  3. Growth Tracking: Track follower growth over time
  4. Network Visualization: Visualize the user's Nostr network
  5. Notification Integration: Notify users of new followers