SubLoop React SDK

Official React SDK for SubLoop's payment analytics API. Includes React hooks and pre-built components for easy integration.

Installation

npm install subloop-react

Quick Start

Basic Setup with Provider

import React from 'react';
import { SubLoopProvider, AnalyticsDashboard } from 'subloop-react';

function App() {
  return (
    <SubLoopProvider apiKey="sk_your_api_key_here">
      <div className="app">
        <h1>My Revenue Dashboard</h1>
        <AnalyticsDashboard autoRefresh={true} />
      </div>
    </SubLoopProvider>
  );
}

export default App;

Using Hooks

import React from 'react';
import { useSubLoop, useAnalytics, usePaymentTracking } from 'subloop-react';

function MyComponent() {
  const client = useSubLoop('sk_your_api_key_here');
  const { data, loading, error, refresh } = useAnalytics(client, { autoRefresh: true });
  const { trackSuccessfulPayment } = usePaymentTracking(client);

  const handlePayment = async () => {
    try {
      await trackSuccessfulPayment('cust_001', 29.99, 'USD', 'sub_001');
      refresh(); // Refresh analytics after payment
    } catch (error) {
      console.error('Payment tracking failed:', error);
    }
  };

  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;

  return (
    <div>
      <h2>Revenue: ${data?.overview?.total_revenue_30d}</h2>
      <button onClick={handlePayment}>Track Payment</button>
    </div>
  );
}

API Reference

Components

SubLoopProvider

Provides SubLoop client context to child components.

<SubLoopProvider apiKey="sk_your_api_key" baseUrl="https://api.getsubloop.com">
  <YourApp />
</SubLoopProvider>

Props:

  • apiKey (string, required): Your SubLoop API key
  • baseUrl (string, optional): API base URL
  • children (ReactNode): Child components

AnalyticsDashboard

Complete analytics dashboard with overview cards, MRR growth, and recent payments.

<AnalyticsDashboard 
  autoRefresh={true}
  refreshInterval={30000}
  showMRRGrowth={true}
  showRecentPayments={true}
  recentPaymentsLimit={5}
/>

Props:

  • autoRefresh (boolean): Enable auto-refresh
  • refreshInterval (number): Refresh interval in milliseconds
  • showMRRGrowth (boolean): Show MRR growth component
  • showRecentPayments (boolean): Show recent payments component
  • recentPaymentsLimit (number): Number of recent payments to show

AnalyticsOverview

Overview metrics cards showing key revenue metrics.

<AnalyticsOverview autoRefresh={true} refreshInterval={30000} />

MRRGrowth

MRR growth component with current/previous MRR and growth percentage.

<MRRGrowth autoRefresh={true} refreshInterval={60000} />

RecentPayments

List of recent payment transactions.

<RecentPayments 
  limit={10} 
  autoRefresh={true} 
  refreshInterval={30000} 
/>

OverviewCard

Reusable metric card component.

<OverviewCard
  title="Monthly Revenue"
  value="$12,345"
  icon="💰"
  color="#3B82F6"
/>

Hooks

useSubLoop

Main hook for creating SubLoop client instance.

const client = useSubLoop(apiKey, baseUrl);

useAnalytics

Hook for fetching complete analytics data.

const { data, loading, error, refresh } = useAnalytics(client, {
  autoRefresh: true,
  refreshInterval: 30000
});

Returns:

  • data: Analytics data object with overview, MRR, and recent payments
  • loading: Loading state
  • error: Error object if request failed
  • refresh: Function to manually refresh data

usePaymentTracking

Hook for tracking payment events.

const { 
  trackPayment, 
  trackSuccessfulPayment, 
  trackFailedPayment,
  loading, 
  error, 
  lastPayment 
} = usePaymentTracking(client);

Returns:

  • trackPayment(data): Track custom payment event
  • trackSuccessfulPayment(customerId, amount, currency, subscriptionId, metadata): Track successful payment
  • trackFailedPayment(customerId, amount, currency, subscriptionId, metadata): Track failed payment
  • loading: Loading state
  • error: Error object if request failed
  • lastPayment: Last tracked payment data

useRevenue

Hook for fetching revenue data.

const { data, loading, error, refresh } = useRevenue(client, '30days', {
  startDate: '2025-06-01',
  endDate: '2025-07-31',
  autoRefresh: true,
  refreshInterval: 60000
});

useMRR

Hook for fetching MRR data.

const { data, loading, error, refresh } = useMRR(client, {
  autoRefresh: true,
  refreshInterval: 60000
});

usePayments

Hook for fetching payments list.

const { data, loading, error, refresh } = usePayments(client, { page: 1 }, {
  autoRefresh: true,
  refreshInterval: 30000
});

Integration Examples

Complete Dashboard App

import React from 'react';
import { 
  SubLoopProvider, 
  AnalyticsDashboard,
  useSubLoopContext,
  usePaymentTracking 
} from 'subloop-react';

function PaymentForm() {
  const client = useSubLoopContext();
  const { trackSuccessfulPayment, loading } = usePaymentTracking(client);

  const [formData, setFormData] = React.useState({
    customerId: '',
    amount: '',
    currency: 'USD'
  });

  const handleSubmit = async (e) => {
    e.preventDefault();
    try {
      await trackSuccessfulPayment(
        formData.customerId,
        parseFloat(formData.amount),
        formData.currency
      );
      alert('Payment tracked successfully!');
      setFormData({ customerId: '', amount: '', currency: 'USD' });
    } catch (error) {
      alert('Error tracking payment: ' + error.message);
    }
  };

  return (
    <form onSubmit={handleSubmit} style={{ marginBottom: '20px' }}>
      <h3>Track New Payment</h3>
      <input
        type="text"
        placeholder="Customer ID"
        value={formData.customerId}
        onChange={(e) => setFormData({...formData, customerId: e.target.value})}
        required
      />
      <input
        type="number"
        placeholder="Amount"
        value={formData.amount}
        onChange={(e) => setFormData({...formData, amount: e.target.value})}
        required
      />
      <select
        value={formData.currency}
        onChange={(e) => setFormData({...formData, currency: e.target.value})}
      >
        <option value="USD">USD</option>
        <option value="EUR">EUR</option>
        <option value="GBP">GBP</option>
      </select>
      <button type="submit" disabled={loading}>
        {loading ? 'Tracking...' : 'Track Payment'}
      </button>
    </form>
  );
}

function App() {
  return (
    <SubLoopProvider apiKey={process.env.REACT_APP_SUBLOOP_API_KEY}>
      <div style={{ padding: '20px' }}>
        <h1>Revenue Analytics Dashboard</h1>
        <PaymentForm />
        <AnalyticsDashboard autoRefresh={true} refreshInterval={30000} />
      </div>
    </SubLoopProvider>
  );
}

export default App;

Custom Analytics Component

import React from 'react';
import { useRevenue, useMRR, useSubLoopContext } from 'subloop-react';

function CustomAnalytics() {
  const client = useSubLoopContext();
  const { data: revenue30d } = useRevenue(client, '30days');
  const { data: revenue7d } = useRevenue(client, '7days');
  const { data: mrr } = useMRR(client);

  return (
    <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: '20px' }}>
      <div style={{ padding: '20px', border: '1px solid #ddd', borderRadius: '8px' }}>
        <h3>7-Day Revenue</h3>
        <p style={{ fontSize: '24px', fontWeight: 'bold' }}>
          ${revenue7d?.total_revenue?.toLocaleString() || '0'}
        </p>
      </div>

      <div style={{ padding: '20px', border: '1px solid #ddd', borderRadius: '8px' }}>
        <h3>30-Day Revenue</h3>
        <p style={{ fontSize: '24px', fontWeight: 'bold' }}>
          ${revenue30d?.total_revenue?.toLocaleString() || '0'}
        </p>
      </div>

      <div style={{ padding: '20px', border: '1px solid #ddd', borderRadius: '8px' }}>
        <h3>MRR Growth</h3>
        <p style={{ fontSize: '24px', fontWeight: 'bold' }}>
          {mrr?.growth_percentage?.toFixed(1) || '0'}%
        </p>
        <p style={{ fontSize: '14px', color: '#666' }}>
          Current: ${mrr?.mrr?.toLocaleString() || '0'}
        </p>
      </div>
    </div>
  );
}

Stripe Integration

import React from 'react';
import { loadStripe } from '@stripe/stripe-js';
import { Elements, CardElement, useStripe, useElements } from '@stripe/react-stripe-js';
import { usePaymentTracking, useSubLoopContext } from 'subloop-react';

const stripePromise = loadStripe('pk_test_your_stripe_key');

function CheckoutForm({ customerId, amount, onSuccess }) {
  const stripe = useStripe();
  const elements = useElements();
  const client = useSubLoopContext();
  const { trackSuccessfulPayment, trackFailedPayment } = usePaymentTracking(client);

  const [loading, setLoading] = React.useState(false);

  const handleSubmit = async (event) => {
    event.preventDefault();

    if (!stripe || !elements) return;

    setLoading(true);

    try {
      // Create payment intent on your backend
      const { client_secret } = await createPaymentIntent(amount);

      // Confirm payment with Stripe
      const { error, paymentIntent } = await stripe.confirmCardPayment(client_secret, {
        payment_method: {
          card: elements.getElement(CardElement),
        }
      });

      if (error) {
        // Track failed payment in SubLoop
        await trackFailedPayment(customerId, amount, 'USD', null, {
          stripe_error: error.message,
          payment_intent_id: paymentIntent?.id
        });
        alert('Payment failed: ' + error.message);
      } else {
        // Track successful payment in SubLoop
        await trackSuccessfulPayment(customerId, amount, 'USD', null, {
          stripe_payment_intent_id: paymentIntent.id,
          stripe_payment_method_id: paymentIntent.payment_method
        });
        onSuccess(paymentIntent);
      }
    } catch (error) {
      console.error('Payment error:', error);
      await trackFailedPayment(customerId, amount, 'USD', null, {
        error: error.message
      });
    } finally {
      setLoading(false);
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <CardElement />
      <button type="submit" disabled={!stripe || loading}>
        {loading ? 'Processing...' : `Pay $${amount}`}
      </button>
    </form>
  );
}

function PaymentPage() {
  return (
    <Elements stripe={stripePromise}>
      <CheckoutForm 
        customerId="cust_001" 
        amount={29.99}
        onSuccess={(paymentIntent) => {
          alert('Payment successful!');
        }}
      />
    </Elements>
  );
}

Real-time Dashboard with WebSockets

import React from 'react';
import { useAnalytics, useSubLoopContext } from 'subloop-react';

function RealTimeDashboard() {
  const client = useSubLoopContext();
  const { data, loading, refresh } = useAnalytics(client);

  React.useEffect(() => {
    // Connect to WebSocket for real-time updates
    const ws = new WebSocket('wss://your-websocket-server.com');

    ws.onmessage = (event) => {
      const message = JSON.parse(event.data);
      if (message.type === 'payment_received') {
        // Refresh analytics when new payment is received
        refresh();
      }
    };

    return () => ws.close();
  }, [refresh]);

  if (loading) return <div>Loading real-time data...</div>;

  return (
    <div>
      <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
        <h2>Real-time Revenue Dashboard</h2>
        <div style={{ 
          backgroundColor: '#10B981', 
          color: 'white', 
          padding: '4px 8px', 
          borderRadius: '4px',
          fontSize: '12px'
        }}>
          🟢 LIVE
        </div>
      </div>

      <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: '20px' }}>
        <div style={{ padding: '20px', backgroundColor: '#f8f9fa', borderRadius: '8px' }}>
          <h3>Total Revenue</h3>
          <p style={{ fontSize: '28px', fontWeight: 'bold', color: '#10B981' }}>
            ${data?.overview?.total_revenue_30d?.toLocaleString() || '0'}
          </p>
        </div>

        <div style={{ padding: '20px', backgroundColor: '#f8f9fa', borderRadius: '8px' }}>
          <h3>Active Subscriptions</h3>
          <p style={{ fontSize: '28px', fontWeight: 'bold', color: '#3B82F6' }}>
            {data?.overview?.active_subscriptions?.toLocaleString() || '0'}
          </p>
        </div>

        <div style={{ padding: '20px', backgroundColor: '#f8f9fa', borderRadius: '8px' }}>
          <h3>Success Rate</h3>
          <p style={{ fontSize: '28px', fontWeight: 'bold', color: '#F59E0B' }}>
            {data?.overview?.payment_success_rate || '0'}%
          </p>
        </div>
      </div>
    </div>
  );
}

Error Handling

import React from 'react';
import { SubLoopError, useAnalytics } from 'subloop-react';

function AnalyticsWithErrorHandling() {
  const { data, loading, error } = useAnalytics(client);

  if (loading) return <div>Loading...</div>;

  if (error) {
    if (error instanceof SubLoopError) {
      if (error.isAuthenticationError()) {
        return <div>Authentication failed. Please check your API key.</div>;
      } else if (error.isRateLimitError()) {
        return <div>Rate limit exceeded. Please try again later.</div>;
      } else if (error.isValidationError()) {
        return <div>Validation error: {JSON.stringify(error.getDetails())}</div>;
      }
    }
    return <div>Error: {error.message}</div>;
  }

  return <div>Analytics data loaded successfully!</div>;
}

TypeScript Support

The SDK includes full TypeScript support:

import React from 'react';
import { SubLoopProvider, useAnalytics, AnalyticsData } from 'subloop-react';

interface DashboardProps {
  apiKey: string;
}

const Dashboard: React.FC<DashboardProps> = ({ apiKey }) => {
  return (
    <SubLoopProvider apiKey={apiKey}>
      <AnalyticsComponent />
    </SubLoopProvider>
  );
};

const AnalyticsComponent: React.FC = () => {
  const client = useSubLoopContext();
  const { data, loading, error }: {
    data: AnalyticsData | null;
    loading: boolean;
    error: Error | null;
  } = useAnalytics(client);

  // Component implementation...
};

Requirements

  • React 16.8.0 or higher (hooks support)
  • Modern browser with fetch support

Support

License

MIT License. See LICENSE for more information.