Skip to main content

SDK Best Practices

Follow these best practices to build high-performance, maintainable plugins with the TagadaPay SDK.
Quick Links:

Performance Optimization

1. Use React.memo for Heavy Components

import React, { memo } from 'react';
import { formatMoney } from '@tagadapay/plugin-sdk';

const ProductCard = memo(({ product, currency }) => {
  return (
    <div className="product-card">
      <h3>{product.name}</h3>
      <p>{formatMoney(product.price, currency)}</p>
    </div>
  );
});

export default ProductCard;

2. Optimize Bundle Size

// ✅ Import only what you need
import { useCheckout, formatMoney } from '@tagadapay/plugin-sdk';

// ❌ Don't import the entire SDK
import * as TagadaSDK from '@tagadapay/plugin-sdk';

3. Lazy Load Components

import { lazy, Suspense } from 'react';

const HeavyComponent = lazy(() => import('./HeavyComponent'));

function MyPlugin() {
  return (
    <Suspense fallback={<div>Loading...</div>}>
      <HeavyComponent />
    </Suspense>
  );
}

State Management

1. Use SDK Hooks Correctly

// ✅ Good: Use hooks at component level
function CheckoutComponent() {
  const { checkout, updateCheckout } = useCheckout();
  
  return <div>{/* component JSX */}</div>;
}

// ❌ Avoid: Don't call hooks conditionally
function BadComponent({ showCheckout }) {
  if (showCheckout) {
    const { checkout } = useCheckout(); // This will cause errors
  }
}

2. Minimize Re-renders

import { useMemo } from 'react';
import { useCheckout } from '@tagadapay/plugin-sdk';

function OptimizedComponent() {
  const { checkout } = useCheckout();
  
  // Memoize expensive calculations
  const totalWithTax = useMemo(() => {
    return checkout.total + checkout.tax;
  }, [checkout.total, checkout.tax]);
  
  return <div>Total: {totalWithTax}</div>;
}

Error Handling

1. Handle Async Operations Properly

import { useState } from 'react';
import { useCheckout, trackEvent } from '@tagadapay/plugin-sdk';

function PaymentButton() {
  const { pay } = useCheckout();
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);

  const handlePayment = async () => {
    setLoading(true);
    setError(null);
    
    try {
      await pay();
      trackEvent('payment_success');
    } catch (err) {
      setError(err.message);
      trackEvent('payment_error', { error: err.message });
    } finally {
      setLoading(false);
    }
  };

  return (
    <div>
      <button onClick={handlePayment} disabled={loading}>
        {loading ? 'Processing...' : 'Pay Now'}
      </button>
      {error && <div className="error">{error}</div>}
    </div>
  );
}

2. Provide Fallbacks

import { useStore } from '@tagadapay/plugin-sdk';

function BrandedComponent() {
  const { branding } = useStore();
  
  // Provide fallback values
  const primaryColor = branding?.primaryColor || '#3b82f6';
  const fontFamily = branding?.fontFamily || 'system-ui, sans-serif';
  
  return (
    <div style={{ color: primaryColor, fontFamily }}>
      Content with fallback styling
    </div>
  );
}

User Experience

1. Show Loading States

function CheckoutForm() {
  const { checkout, updateCustomer, loading } = useCheckout();
  
  if (loading) {
    return (
      <div className="loading-state">
        <div className="spinner" />
        <p>Loading your checkout...</p>
      </div>
    );
  }
  
  return <form>{/* checkout form */}</form>;
}

2. Provide Clear Feedback

import { useState } from 'react';
import { usePromotionCodes } from '@tagadapay/plugin-sdk';

function PromoCodeInput() {
  const { applyCode } = usePromotionCodes();
  const [code, setCode] = useState('');
  const [status, setStatus] = useState('idle'); // idle, applying, success, error

  const handleApply = async () => {
    setStatus('applying');
    
    try {
      await applyCode(code);
      setStatus('success');
      setCode('');
    } catch (error) {
      setStatus('error');
    }
  };

  return (
    <div>
      <input 
        value={code} 
        onChange={(e) => setCode(e.target.value)}
        placeholder="Enter promo code"
      />
      <button onClick={handleApply} disabled={status === 'applying'}>
        {status === 'applying' ? 'Applying...' : 'Apply'}
      </button>
      
      {status === 'success' && (
        <div className="success">✅ Promo code applied!</div>
      )}
      {status === 'error' && (
        <div className="error">❌ Invalid promo code</div>
      )}
    </div>
  );
}

Accessibility

1. Use Semantic HTML

function AccessibleForm() {
  return (
    <form>
      <fieldset>
        <legend>Contact Information</legend>
        
        <label htmlFor="email">
          Email Address *
          <input 
            id="email" 
            type="email" 
            required 
            aria-describedby="email-error"
          />
        </label>
        
        <div id="email-error" role="alert">
          {/* Error message */}
        </div>
      </fieldset>
    </form>
  );
}

2. Provide Keyboard Navigation

function KeyboardFriendlyComponent() {
  const handleKeyDown = (e) => {
    if (e.key === 'Enter' || e.key === ' ') {
      e.preventDefault();
      handleAction();
    }
  };

  return (
    <div 
      role="button"
      tabIndex={0}
      onKeyDown={handleKeyDown}
      onClick={handleAction}
    >
      Interactive Element
    </div>
  );
}

Security

1. Sanitize User Input

import DOMPurify from 'dompurify';

function UserContentDisplay({ userContent }) {
  const sanitizedContent = DOMPurify.sanitize(userContent);
  
  return (
    <div dangerouslySetInnerHTML={{ __html: sanitizedContent }} />
  );
}

2. Handle Sensitive Data Carefully

// ✅ Good: Don't log sensitive data
function PaymentComponent() {
  const handlePayment = (paymentData) => {
    console.log('Processing payment...'); // Don't log payment details
    
    // Process payment
  };
}

// ❌ Avoid: Logging sensitive information
function BadPaymentComponent() {
  const handlePayment = (paymentData) => {
    console.log('Payment data:', paymentData); // This could expose sensitive info
  };
}

Testing

1. Test with Different Store Configurations

// Test with various store setups
const testStores = [
  { currency: 'USD', locale: 'en-US' },
  { currency: 'EUR', locale: 'de-DE' },
  { currency: 'JPY', locale: 'ja-JP' }
];

testStores.forEach(store => {
  test(`works with ${store.currency} currency`, () => {
    // Test your component with different store configs
  });
});

2. Mock SDK Dependencies

// __mocks__/@tagadapay/plugin-sdk.js
export const useCheckout = jest.fn(() => ({
  checkout: {
    total: 2999,
    currency: 'USD',
    items: []
  },
  updateCheckout: jest.fn(),
  pay: jest.fn()
}));

export const formatMoney = jest.fn((amount, currency) => 
  `${currency} ${(amount / 100).toFixed(2)}`
);

Code Organization

1. Use Custom Hooks

// hooks/useFormValidation.ts
import { useState, useCallback } from 'react';

export function useFormValidation(validationRules) {
  const [errors, setErrors] = useState({});
  
  const validate = useCallback((data) => {
    const newErrors = {};
    
    Object.keys(validationRules).forEach(field => {
      const rule = validationRules[field];
      if (!rule(data[field])) {
        newErrors[field] = `${field} is invalid`;
      }
    });
    
    setErrors(newErrors);
    return Object.keys(newErrors).length === 0;
  }, [validationRules]);
  
  return { errors, validate };
}

2. Separate Business Logic

// utils/checkoutHelpers.ts
export function calculateTotal(items, tax = 0, shipping = 0) {
  const subtotal = items.reduce((sum, item) => 
    sum + (item.price * item.quantity), 0
  );
  
  return subtotal + tax + shipping;
}

export function validateEmail(email) {
  return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}

// components/CheckoutForm.tsx
import { calculateTotal, validateEmail } from '../utils/checkoutHelpers';

Configuration Management

1. Use Environment-Specific Settings

const config = {
  development: {
    apiUrl: 'https://app.tagadapay.dev',
    debug: true
  },
  production: {
    apiUrl: 'https://app.tagadapay.com',
    debug: false
  }
}[process.env.NODE_ENV || 'development'];

2. Make Components Configurable

interface PluginConfig {
  showUpsells: boolean;
  maxUpsellCount: number;
  theme: 'light' | 'dark';
}

function ConfigurablePlugin({ config }: { config: PluginConfig }) {
  return (
    <div className={`theme-${config.theme}`}>
      {/* Main content */}
      {config.showUpsells && (
        <UpsellSection maxCount={config.maxUpsellCount} />
      )}
    </div>
  );
}

Monitoring and Analytics

1. Track Important Events

import { trackEvent } from '@tagadapay/plugin-sdk';

function TrackingComponent() {
  useEffect(() => {
    trackEvent('plugin_loaded', {
      version: '1.2.0',
      timestamp: Date.now()
    });
  }, []);

  const handleUserAction = (action) => {
    trackEvent('user_interaction', {
      action,
      timestamp: Date.now()
    });
  };
}

2. Monitor Performance

function PerformanceMonitor({ children }) {
  useEffect(() => {
    const startTime = performance.now();
    
    return () => {
      const loadTime = performance.now() - startTime;
      trackEvent('plugin_performance', {
        loadTime,
        component: 'main'
      });
    };
  }, []);

  return children;
}
Following these best practices will help you build robust, performant, and maintainable plugins that provide excellent user experiences across different store configurations and environments.