Adopture

Practical Examples

Real-world examples of tracking feature adoption in Next.js applications

Practical Examples

Learn from real-world examples of how to implement feature adoption tracking in common scenarios.

E-commerce Product Page

Track user interactions on a product page:

components/ProductPage.tsx
'use client';

import { useTrack, useComponentTracking } from '@adopture/next';
import { useState } from 'react';

export function ProductPage({ productId }: { productId: string }) {
  const { track } = useTrack();
  const [selectedVariant, setSelectedVariant] = useState('default');

  // Track page exposure
  useComponentTracking('product-page-view', {
    exposureChannel: 'product-detail',
    metadata: {
      productId,
      category: 'electronics',
    },
  });

  const handleAddToCart = () => {
    track('add-to-cart-clicked', {
      productId,
      variant: selectedVariant,
      price: 99.99,
      source: 'product-page',
    });
  };

  const handleWishlist = () => {
    track('wishlist-added', {
      productId,
      source: 'product-page',
    });
  };

  const handleImageZoom = () => {
    track('product-image-zoomed', {
      productId,
      imageIndex: 0,
    });
  };

  return (
    <div className="product-page">
      <img
        src="/product.jpg"
        alt="Product"
        onClick={handleImageZoom}
      />

      <select
        value={selectedVariant}
        onChange={(e) => {
          setSelectedVariant(e.target.value);
          track('product-variant-selected', {
            productId,
            variant: e.target.value,
          });
        }}
      >
        <option value="default">Default</option>
        <option value="premium">Premium</option>
      </select>

      <button onClick={handleAddToCart}>
        Add to Cart
      </button>

      <button onClick={handleWishlist}>
        Add to Wishlist
      </button>
    </div>
  );
}

User Onboarding Flow

Track user progress through an onboarding sequence:

components/OnboardingFlow.tsx
'use client';

import { useTrack, useIdentify } from '@adopture/next';
import { useState } from 'react';

export function OnboardingFlow() {
  const { track } = useTrack();
  const { identify } = useIdentify();
  const [step, setStep] = useState(1);
  const [userData, setUserData] = useState({});

  const handleStepComplete = (stepNumber: number, data: any) => {
    track('onboarding-step-completed', {
      step: stepNumber,
      totalSteps: 4,
      data,
      timeSpent: Date.now() - stepStartTime,
    });
  };

  const handleProfileSetup = (profileData: any) => {
    // Identify user with their profile
    identify(profileData.userId, {
      name: profileData.name,
      email: profileData.email,
      company: profileData.company,
      role: profileData.role,
      source: 'onboarding',
    });

    handleStepComplete(1, profileData);
    setStep(2);
  };

  const handlePreferencesSet = (preferences: any) => {
    track('preferences-configured', {
      notifications: preferences.notifications,
      theme: preferences.theme,
      language: preferences.language,
    });

    handleStepComplete(2, preferences);
    setStep(3);
  };

  const handleIntegrationConnected = (integrationId: string) => {
    track('integration-connected', {
      integration: integrationId,
      step: 'onboarding',
    });

    handleStepComplete(3, { integration: integrationId });
    setStep(4);
  };

  const handleOnboardingComplete = () => {
    track('onboarding-completed', {
      totalTime: Date.now() - onboardingStartTime,
      stepsCompleted: 4,
      completionRate: 100,
    });
  };

  return (
    <div className="onboarding-flow">
      {step === 1 && (
        <ProfileSetup onComplete={handleProfileSetup} />
      )}
      {step === 2 && (
        <PreferencesStep onComplete={handlePreferencesSet} />
      )}
      {step === 3 && (
        <IntegrationStep onComplete={handleIntegrationConnected} />
      )}
      {step === 4 && (
        <WelcomeStep onComplete={handleOnboardingComplete} />
      )}
    </div>
  );
}

Dashboard Analytics Widget

Track interactions with analytics widgets:

components/AnalyticsWidget.tsx
'use client';

import { useTrack, useComponentTracking } from '@adopture/next';
import { useState } from 'react';

type TimeRange = '7d' | '30d' | '90d';

export function AnalyticsWidget({ widgetType }: { widgetType: string }) {
  const { track } = useTrack();
  const [timeRange, setTimeRange] = useState<TimeRange>('7d');
  const [isExpanded, setIsExpanded] = useState(false);

  // Track widget visibility
  useComponentTracking(`analytics-widget-${widgetType}`, {
    exposureChannel: 'dashboard',
    metadata: {
      widgetType,
      position: 'main-dashboard',
    },
  });

  const handleTimeRangeChange = (newRange: TimeRange) => {
    track('analytics-timerange-changed', {
      widgetType,
      fromRange: timeRange,
      toRange: newRange,
      section: 'dashboard',
    });
    setTimeRange(newRange);
  };

  const handleWidgetExpand = () => {
    track('analytics-widget-expanded', {
      widgetType,
      previousState: isExpanded ? 'expanded' : 'collapsed',
    });
    setIsExpanded(!isExpanded);
  };

  const handleExportData = () => {
    track('analytics-data-exported', {
      widgetType,
      timeRange,
      format: 'csv',
    });
  };

  const handleRefresh = () => {
    track('analytics-widget-refreshed', {
      widgetType,
      timeRange,
      manual: true,
    });
  };

  return (
    <div className="analytics-widget">
      <div className="widget-header">
        <h3>{widgetType} Analytics</h3>

        <select
          value={timeRange}
          onChange={(e) => handleTimeRangeChange(e.target.value as TimeRange)}
        >
          <option value="7d">Last 7 days</option>
          <option value="30d">Last 30 days</option>
          <option value="90d">Last 90 days</option>
        </select>

        <button onClick={handleRefresh}>
          Refresh
        </button>

        <button onClick={handleWidgetExpand}>
          {isExpanded ? 'Collapse' : 'Expand'}
        </button>
      </div>

      <div className="widget-content">
        {/* Chart content */}
      </div>

      <div className="widget-actions">
        <button onClick={handleExportData}>
          Export Data
        </button>
      </div>
    </div>
  );
}

Feature Flag Integration

Track feature flag exposures and interactions:

components/FeatureFlagExample.tsx
'use client';

import { useTrack, useExpose } from '@adopture/next';
import { useFeatureFlag } from '@/hooks/useFeatureFlag';
import { useEffect } from 'react';

export function FeatureFlagExample() {
  const { track } = useTrack();
  const { expose } = useExpose();
  const isNewFeatureEnabled = useFeatureFlag('new-dashboard-design');

  useEffect(() => {
    // Track feature flag exposure
    expose('new-dashboard-design-flag', 'feature-flag', {
      enabled: isNewFeatureEnabled,
      variant: isNewFeatureEnabled ? 'treatment' : 'control',
      userId: 'current-user-id',
    });
  }, [isNewFeatureEnabled, expose]);

  const handleNewFeatureClick = () => {
    track('new-feature-clicked', {
      featureFlag: 'new-dashboard-design',
      variant: 'treatment',
      source: 'feature-banner',
    });
  };

  if (!isNewFeatureEnabled) {
    return <div>Old dashboard design</div>;
  }

  return (
    <div className="new-dashboard">
      <button onClick={handleNewFeatureClick}>
        Try New Feature
      </button>
    </div>
  );
}

Search and Filtering

Track user search behavior and filter usage:

components/SearchExample.tsx
'use client';

import { useTrack } from '@adopture/next';
import { useState, useCallback } from 'react';
import { debounce } from 'lodash';

export function SearchExample() {
  const { track } = useTrack();
  const [searchTerm, setSearchTerm] = useState('');
  const [filters, setFilters] = useState({
    category: '',
    priceRange: '',
    rating: '',
  });

  const debouncedTrackSearch = useCallback(
    debounce((term: string) => {
      if (term.length > 2) {
        track('search-performed', {
          query: term,
          queryLength: term.length,
          source: 'main-search',
        });
      }
    }, 500),
    [track]
  );

  const handleSearchChange = (value: string) => {
    setSearchTerm(value);
    debouncedTrackSearch(value);
  };

  const handleFilterChange = (filterType: string, value: string) => {
    const newFilters = { ...filters, [filterType]: value };
    setFilters(newFilters);

    track('search-filter-applied', {
      filterType,
      filterValue: value,
      activeFilters: Object.keys(newFilters).filter(k => newFilters[k]),
      searchTerm,
    });
  };

  const handleSearchSubmit = () => {
    track('search-submitted', {
      query: searchTerm,
      filters: Object.keys(filters).filter(k => filters[k]),
      method: 'submit-button',
    });
  };

  return (
    <div className="search-interface">
      <input
        type="text"
        value={searchTerm}
        onChange={(e) => handleSearchChange(e.target.value)}
        placeholder="Search products..."
      />

      <select
        value={filters.category}
        onChange={(e) => handleFilterChange('category', e.target.value)}
      >
        <option value="">All Categories</option>
        <option value="electronics">Electronics</option>
        <option value="clothing">Clothing</option>
      </select>

      <button onClick={handleSearchSubmit}>
        Search
      </button>
    </div>
  );
}

User Authentication Flow

Track user login, registration, and authentication events:

components/AuthExample.tsx
'use client';

import { useTrack, useIdentify } from '@adopture/next';
import { useState } from 'react';

export function AuthExample() {
  const { track } = useTrack();
  const { identify } = useIdentify();
  const [authMode, setAuthMode] = useState<'login' | 'register'>('login');

  const handleLoginAttempt = async (email: string, method: string) => {
    track('login-attempted', {
      method, // 'email', 'google', 'github'
      email: email.includes('@') ? 'valid' : 'invalid',
    });
  };

  const handleLoginSuccess = async (userId: string, userProperties: any) => {
    // Identify the user
    await identify(userId, {
      ...userProperties,
      lastLogin: new Date().toISOString(),
    });

    track('login-successful', {
      method: userProperties.loginMethod,
      isReturningUser: userProperties.isReturningUser,
      accountAge: userProperties.accountAge,
    });
  };

  const handleRegistrationStart = () => {
    track('registration-started', {
      source: 'auth-modal',
      previousAction: 'login-form',
    });
    setAuthMode('register');
  };

  const handleRegistrationComplete = async (userId: string, userData: any) => {
    // Identify new user
    await identify(userId, {
      ...userData,
      registrationDate: new Date().toISOString(),
      isNewUser: true,
    });

    track('registration-completed', {
      method: userData.registrationMethod,
      referralSource: userData.referralSource,
      completionTime: Date.now() - registrationStartTime,
    });
  };

  const handlePasswordReset = (email: string) => {
    track('password-reset-requested', {
      emailValid: email.includes('@'),
      source: 'login-form',
    });
  };

  return (
    <div className="auth-interface">
      {authMode === 'login' ? (
        <LoginForm
          onLoginAttempt={handleLoginAttempt}
          onLoginSuccess={handleLoginSuccess}
          onPasswordReset={handlePasswordReset}
          onSwitchToRegister={handleRegistrationStart}
        />
      ) : (
        <RegisterForm
          onRegistrationComplete={handleRegistrationComplete}
        />
      )}
    </div>
  );
}

Form Tracking with Validation

Track form interactions, validation errors, and completion:

components/ContactForm.tsx
'use client';

import { useTrack } from '@adopture/next';
import { useState, useEffect } from 'react';

export function ContactForm() {
  const { track } = useTrack();
  const [formData, setFormData] = useState({
    name: '',
    email: '',
    subject: '',
    message: '',
  });
  const [errors, setErrors] = useState({});
  const [startTime, setStartTime] = useState(Date.now());

  useEffect(() => {
    // Track form start
    track('contact-form-started', {
      source: 'contact-page',
      timestamp: new Date().toISOString(),
    });
    setStartTime(Date.now());
  }, [track]);

  const handleFieldChange = (field: string, value: string) => {
    setFormData(prev => ({ ...prev, [field]: value }));

    // Track field completion for longer fields
    if (field === 'message' && value.length > 50) {
      track('form-field-detailed', {
        field,
        length: value.length,
        formType: 'contact',
      });
    }
  };

  const handleValidationError = (field: string, errorType: string) => {
    track('form-validation-error', {
      field,
      errorType,
      formType: 'contact',
      attempt: validationAttempts[field] || 1,
    });
  };

  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault();

    const completionTime = Date.now() - startTime;
    const fieldsCompleted = Object.keys(formData).filter(k => formData[k]);

    track('contact-form-submitted', {
      completionTime,
      fieldsCompleted: fieldsCompleted.length,
      totalFields: Object.keys(formData).length,
      hasErrors: Object.keys(errors).length > 0,
      messageLength: formData.message.length,
    });
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        type="text"
        value={formData.name}
        onChange={(e) => handleFieldChange('name', e.target.value)}
        placeholder="Your name"
      />

      <input
        type="email"
        value={formData.email}
        onChange={(e) => handleFieldChange('email', e.target.value)}
        placeholder="Your email"
      />

      <textarea
        value={formData.message}
        onChange={(e) => handleFieldChange('message', e.target.value)}
        placeholder="Your message"
        rows={5}
      />

      <button type="submit">Send Message</button>
    </form>
  );
}

Next Steps

These examples show common patterns for tracking user interactions. You can adapt them to your specific use cases:

Tips for Success

  1. Start simple: Begin with basic click tracking, then add more detailed events
  2. Be consistent: Use consistent naming conventions across your application
  3. Add context: Include relevant metadata to make your analytics more actionable
  4. Respect privacy: Don't track sensitive user information
  5. Test thoroughly: Verify events are firing correctly before deploying