Analytics Events
The @54vie/analytics package provides a comprehensive event tracking system with predefined standard events and support for custom event tracking. This guide covers the StandardEvents enum, event properties, and best practices for event tracking.
Installation
npm install @54vie/analytics
# or
yarn add @54vie/analytics
StandardEvents Enum
The StandardEvents enum provides predefined event names for common user interactions. Using standard events ensures consistent naming and enables automatic reporting in the 54Vie dashboard.
Import
import { StandardEvents } from '@54vie/analytics';
Available Standard Events
enum StandardEvents {
// Page & Navigation
PAGE_VIEW = 'page_view',
PAGE_LEAVE = 'page_leave',
SCREEN_VIEW = 'screen_view',
// Session
SESSION_START = 'session_start',
SESSION_END = 'session_end',
// User Authentication
SIGN_UP = 'sign_up',
LOGIN = 'login',
LOGOUT = 'logout',
PASSWORD_RESET = 'password_reset',
// E-commerce
VIEW_ITEM = 'view_item',
VIEW_ITEM_LIST = 'view_item_list',
SELECT_ITEM = 'select_item',
ADD_TO_CART = 'add_to_cart',
REMOVE_FROM_CART = 'remove_from_cart',
VIEW_CART = 'view_cart',
BEGIN_CHECKOUT = 'begin_checkout',
ADD_SHIPPING_INFO = 'add_shipping_info',
ADD_PAYMENT_INFO = 'add_payment_info',
PURCHASE = 'purchase',
REFUND = 'refund',
// Engagement
SEARCH = 'search',
SHARE = 'share',
GENERATE_LEAD = 'generate_lead',
SUBSCRIBE = 'subscribe',
UNSUBSCRIBE = 'unsubscribe',
// Content
VIEW_CONTENT = 'view_content',
CONTENT_INTERACTION = 'content_interaction',
VIDEO_START = 'video_start',
VIDEO_PROGRESS = 'video_progress',
VIDEO_COMPLETE = 'video_complete',
// Forms
FORM_START = 'form_start',
FORM_SUBMIT = 'form_submit',
FORM_ERROR = 'form_error',
FORM_ABANDON = 'form_abandon',
// Errors
ERROR = 'error',
EXCEPTION = 'exception',
// Experiments
EXPERIMENT_EXPOSURE = 'experiment_exposure',
EXPERIMENT_CONVERSION = 'experiment_conversion',
// Feature Flags
FEATURE_FLAG_EVALUATED = 'feature_flag_evaluated',
// Custom Conversion Goals
CONVERSION = 'conversion',
GOAL_COMPLETE = 'goal_complete',
}
TypeScript Interfaces
Event Properties
/**
* Base event properties that can be attached to any event
*/
interface EventProperties {
[key: string]: EventPropertyValue;
}
type EventPropertyValue =
| string
| number
| boolean
| null
| undefined
| string[]
| number[]
| Record<string, unknown>;
/**
* Properties specific to e-commerce events
*/
interface EcommerceEventProperties {
currency?: string;
value?: number;
items?: EcommerceItem[];
coupon?: string;
discount?: number;
shipping?: number;
tax?: number;
transactionId?: string;
affiliation?: string;
}
interface EcommerceItem {
itemId: string;
itemName: string;
price?: number;
quantity?: number;
category?: string;
brand?: string;
variant?: string;
position?: number;
coupon?: string;
discount?: number;
}
/**
* Properties for page view events
*/
interface PageViewProperties {
path?: string;
title?: string;
referrer?: string;
search?: string;
hash?: string;
url?: string;
}
/**
* Properties for search events
*/
interface SearchProperties {
searchTerm: string;
resultsCount?: number;
category?: string;
filters?: Record<string, unknown>;
}
/**
* Properties for error events
*/
interface ErrorEventProperties {
errorMessage: string;
errorCode?: string;
errorType?: string;
stackTrace?: string;
componentName?: string;
fatal?: boolean;
}
/**
* Properties for form events
*/
interface FormEventProperties {
formId?: string;
formName?: string;
formStep?: number;
fieldName?: string;
fieldValue?: unknown;
validationError?: string;
}
/**
* Properties for video events
*/
interface VideoEventProperties {
videoId: string;
videoTitle?: string;
videoDuration?: number;
currentTime?: number;
percentComplete?: number;
provider?: string;
}
Tracking Events
Using the useAnalytics Hook
import { useAnalytics, StandardEvents } from '@54vie/analytics';
function ProductCard({ product }) {
const { track, trackStandard } = useAnalytics();
const handleViewProduct = () => {
trackStandard(StandardEvents.VIEW_ITEM, {
itemId: product.id,
itemName: product.name,
price: product.price,
currency: 'USD',
category: product.category,
brand: product.brand,
});
};
const handleAddToCart = () => {
trackStandard(StandardEvents.ADD_TO_CART, {
currency: 'USD',
value: product.price,
items: [
{
itemId: product.id,
itemName: product.name,
price: product.price,
quantity: 1,
category: product.category,
},
],
});
};
return (
<div onClick={handleViewProduct}>
<h3>{product.name}</h3>
<button onClick={handleAddToCart}>Add to Cart</button>
</div>
);
}
Custom Event Tracking
import { useAnalytics } from '@54vie/analytics';
function FeatureComponent() {
const { track } = useAnalytics();
const handleFeatureUsed = () => {
track('feature_used', {
featureName: 'advanced_filters',
filterType: 'date_range',
filterValue: 'last_30_days',
userPlan: 'premium',
});
};
const handleCustomAction = () => {
// Track with nested properties
track('custom_action', {
actionType: 'export',
format: 'csv',
recordCount: 150,
filters: {
status: 'active',
dateRange: 'last_month',
},
metadata: {
source: 'dashboard',
trigger: 'button_click',
},
});
};
return (
<div>
<button onClick={handleFeatureUsed}>Use Feature</button>
<button onClick={handleCustomAction}>Export Data</button>
</div>
);
}
E-commerce Event Examples
Complete Purchase Flow
import { useAnalytics, StandardEvents } from '@54vie/analytics';
function CheckoutFlow() {
const { trackStandard } = useAnalytics();
// Track when user views product
const trackViewItem = (product: Product) => {
trackStandard(StandardEvents.VIEW_ITEM, {
currency: 'USD',
value: product.price,
items: [
{
itemId: product.id,
itemName: product.name,
price: product.price,
category: product.category,
brand: product.brand,
},
],
});
};
// Track when user adds to cart
const trackAddToCart = (product: Product, quantity: number) => {
trackStandard(StandardEvents.ADD_TO_CART, {
currency: 'USD',
value: product.price * quantity,
items: [
{
itemId: product.id,
itemName: product.name,
price: product.price,
quantity,
category: product.category,
},
],
});
};
// Track checkout initiation
const trackBeginCheckout = (cart: CartItem[]) => {
const total = cart.reduce((sum, item) => sum + item.price * item.quantity, 0);
trackStandard(StandardEvents.BEGIN_CHECKOUT, {
currency: 'USD',
value: total,
items: cart.map((item) => ({
itemId: item.id,
itemName: item.name,
price: item.price,
quantity: item.quantity,
})),
});
};
// Track shipping info added
const trackShippingInfo = (shippingMethod: string) => {
trackStandard(StandardEvents.ADD_SHIPPING_INFO, {
shippingMethod,
shippingCost: getShippingCost(shippingMethod),
});
};
// Track payment info added
const trackPaymentInfo = (paymentMethod: string) => {
trackStandard(StandardEvents.ADD_PAYMENT_INFO, {
paymentMethod,
});
};
// Track completed purchase
const trackPurchase = (order: Order) => {
trackStandard(StandardEvents.PURCHASE, {
transactionId: order.id,
currency: 'USD',
value: order.total,
tax: order.tax,
shipping: order.shippingCost,
coupon: order.couponCode,
discount: order.discount,
items: order.items.map((item) => ({
itemId: item.productId,
itemName: item.name,
price: item.price,
quantity: item.quantity,
category: item.category,
})),
});
};
// Track refund
const trackRefund = (orderId: string, items?: RefundItem[]) => {
trackStandard(StandardEvents.REFUND, {
transactionId: orderId,
currency: 'USD',
value: items?.reduce((sum, item) => sum + item.refundAmount, 0),
items: items?.map((item) => ({
itemId: item.productId,
itemName: item.name,
price: item.refundAmount,
quantity: item.quantity,
})),
});
};
return <div>{/* Checkout UI */}</div>;
}
Form Tracking
import { useAnalytics, StandardEvents } from '@54vie/analytics';
import { useState, useEffect } from 'react';
function ContactForm() {
const { trackStandard } = useAnalytics();
const [formStarted, setFormStarted] = useState(false);
// Track form start on first interaction
const handleFieldFocus = () => {
if (!formStarted) {
setFormStarted(true);
trackStandard(StandardEvents.FORM_START, {
formId: 'contact-form',
formName: 'Contact Us',
});
}
};
// Track form submission
const handleSubmit = async (data: FormData) => {
try {
await submitForm(data);
trackStandard(StandardEvents.FORM_SUBMIT, {
formId: 'contact-form',
formName: 'Contact Us',
success: true,
});
} catch (error) {
trackStandard(StandardEvents.FORM_ERROR, {
formId: 'contact-form',
formName: 'Contact Us',
errorMessage: error.message,
});
}
};
// Track form abandonment
useEffect(() => {
return () => {
if (formStarted) {
trackStandard(StandardEvents.FORM_ABANDON, {
formId: 'contact-form',
formName: 'Contact Us',
});
}
};
}, [formStarted]);
return (
<form onSubmit={handleSubmit}>
<input onFocus={handleFieldFocus} name="email" />
<textarea onFocus={handleFieldFocus} name="message" />
<button type="submit">Send</button>
</form>
);
}
Video Tracking
import { useAnalytics, StandardEvents } from '@54vie/analytics';
import { useRef, useEffect } from 'react';
function VideoPlayer({ videoId, title, duration }) {
const { trackStandard } = useAnalytics();
const videoRef = useRef<HTMLVideoElement>(null);
const trackedMilestones = useRef(new Set<number>());
const trackVideoStart = () => {
trackStandard(StandardEvents.VIDEO_START, {
videoId,
videoTitle: title,
videoDuration: duration,
});
};
const trackVideoProgress = (percent: number) => {
if (trackedMilestones.current.has(percent)) return;
trackedMilestones.current.add(percent);
trackStandard(StandardEvents.VIDEO_PROGRESS, {
videoId,
videoTitle: title,
videoDuration: duration,
percentComplete: percent,
currentTime: videoRef.current?.currentTime,
});
};
const trackVideoComplete = () => {
trackStandard(StandardEvents.VIDEO_COMPLETE, {
videoId,
videoTitle: title,
videoDuration: duration,
});
};
const handleTimeUpdate = () => {
const video = videoRef.current;
if (!video) return;
const percent = Math.floor((video.currentTime / video.duration) * 100);
// Track at 25%, 50%, 75% milestones
if (percent >= 25 && percent < 50) trackVideoProgress(25);
else if (percent >= 50 && percent < 75) trackVideoProgress(50);
else if (percent >= 75 && percent < 100) trackVideoProgress(75);
};
return (
<video
ref={videoRef}
onPlay={trackVideoStart}
onTimeUpdate={handleTimeUpdate}
onEnded={trackVideoComplete}
>
<source src={`/videos/${videoId}.mp4`} />
</video>
);
}
Error Tracking
import { useAnalytics, StandardEvents } from '@54vie/analytics';
import { ErrorBoundary } from 'react-error-boundary';
function useErrorTracking() {
const { trackStandard } = useAnalytics();
const trackError = (error: Error, context?: Record<string, unknown>) => {
trackStandard(StandardEvents.ERROR, {
errorMessage: error.message,
errorType: error.name,
stackTrace: error.stack,
fatal: false,
...context,
});
};
const trackException = (error: Error, componentName?: string) => {
trackStandard(StandardEvents.EXCEPTION, {
errorMessage: error.message,
errorType: error.name,
stackTrace: error.stack,
componentName,
fatal: true,
});
};
return { trackError, trackException };
}
// Usage with Error Boundary
function App() {
const { trackException } = useErrorTracking();
return (
<ErrorBoundary
onError={(error, info) => {
trackException(error, info.componentStack);
}}
fallback={<ErrorFallback />}
>
<MainApp />
</ErrorBoundary>
);
}
Search Tracking
import { useAnalytics, StandardEvents } from '@54vie/analytics';
import { useState } from 'react';
function SearchComponent() {
const { trackStandard } = useAnalytics();
const [searchTerm, setSearchTerm] = useState('');
const handleSearch = async () => {
const results = await performSearch(searchTerm);
trackStandard(StandardEvents.SEARCH, {
searchTerm,
resultsCount: results.length,
category: 'products',
filters: {
inStock: true,
priceRange: '$50-$100',
},
});
};
const handleSelectResult = (item: SearchResult, position: number) => {
trackStandard(StandardEvents.SELECT_ITEM, {
itemId: item.id,
itemName: item.name,
itemListName: 'search_results',
itemListId: 'search',
position,
searchTerm,
});
};
return (
<div>
<input
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
onKeyDown={(e) => e.key === 'Enter' && handleSearch()}
/>
<button onClick={handleSearch}>Search</button>
</div>
);
}
Event Validation
The SDK includes built-in validation for event names and properties.
interface EventValidationConfig {
/**
* Maximum length for event names
* @default 100
*/
maxEventNameLength: number;
/**
* Maximum number of properties per event
* @default 100
*/
maxPropertiesCount: number;
/**
* Maximum length for property keys
* @default 100
*/
maxPropertyKeyLength: number;
/**
* Maximum length for string property values
* @default 1000
*/
maxPropertyValueLength: number;
/**
* List of reserved event names that cannot be used
*/
reservedEventNames: string[];
/**
* List of reserved property keys that cannot be used
*/
reservedPropertyKeys: string[];
}
Validation Rules
- Event names must be non-empty strings under 100 characters
- Event names should use snake_case format
- Property keys must be non-empty strings under 100 characters
- String property values are truncated at 1000 characters
- Maximum 100 properties per event
- Reserved prefixes (
_,54vie_) are reserved for internal use
Best Practices
Event Naming Conventions
// Good - use snake_case, be specific
track('button_click', { buttonId: 'signup_cta' });
track('user_profile_updated', { fields: ['email', 'name'] });
track('feature_flag_evaluated', { flagName: 'new_checkout' });
// Bad - avoid these patterns
track('ButtonClick', {}); // Don't use PascalCase
track('button-click', {}); // Don't use kebab-case
track('click', {}); // Too generic
track('user did something', {}); // Don't use spaces
Property Best Practices
// Good - structured, consistent properties
track('purchase_completed', {
orderId: 'ORD-12345',
totalAmount: 99.99,
currency: 'USD',
itemCount: 3,
paymentMethod: 'credit_card',
isFirstPurchase: true,
});
// Bad - avoid these patterns
track('purchase', {
'order-id': '12345', // Use camelCase for keys
total: '$99.99', // Use numbers for numeric values
items: 'item1, item2', // Use arrays for lists
});
Avoiding PII in Events
// Good - use IDs and hashed values
track('user_action', {
userId: user.id,
emailDomain: extractDomain(user.email), // e.g., 'gmail.com'
hasPhone: !!user.phone,
});
// Bad - never include PII directly
track('user_action', {
email: user.email, // Don't include email
phone: user.phone, // Don't include phone
ssn: user.ssn, // Never include sensitive data
});
Related
- Provider - AnalyticsProvider setup and configuration
- Hooks - useAnalytics, useUserProperties, useExperiment
- Experiments - A/B testing and feature flags