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:
'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:
'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:
'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:
'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:
'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:
'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:
'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
- Start simple: Begin with basic click tracking, then add more detailed events
- Be consistent: Use consistent naming conventions across your application
- Add context: Include relevant metadata to make your analytics more actionable
- Respect privacy: Don't track sensitive user information
- Test thoroughly: Verify events are firing correctly before deploying