DEV Community

Cover image for React Google Analytics 4 Tutorial: Type-Safe GA4 Implementation with Ecommerce Tracking
Shiva Aryal
Shiva Aryal

Posted on

React Google Analytics 4 Tutorial: Type-Safe GA4 Implementation with Ecommerce Tracking

Google Analytics 4 (GA4) is powerful — but let's be honest, the default gtag snippets get messy fast. I tried multiple react ga4 libraries to implement on my ecommerce website and all provides the wrapper only. That's why I built a library @connectaryal/google-analytics — a type-safe, developer-friendly GA4 wrapper for React and Next.js.

No more guesswork, no more typos. Just clean analytics tracking.

What You Get

  • Easy initialization with a singleton instance
  • Type-safe events (no more "pageveiw" mistakes)
  • Ready-to-use methods for interaction, auth, and e-commerce
  • Debug mode for development
  • Works in both React & Next.js

Step 1: Create Your Google Analytics 4 Property

Before writing any code, set up your GA4 property:

  1. Visit Google Analytics.
  2. Click Admin (bottom left) > Create Property.
  3. Name your property, timezone/currency, and click Next.
  4. Add a new Web data stream for your site.
  5. Copy your Measurement ID (looks like G-XXXXXXXXXX).

Step 2: Install the npm Package

Skip the manual gtag script and use a robust, type-safe library:

npm install @connectaryal/google-analytics
# or
yarn add @connectaryal/google-analytics
# or
pnpm add @connectaryal/google-analytics
Enter fullscreen mode Exit fullscreen mode

See the package on npm: @connectaryal/google-analytics


Step 3: Initialize Analytics

The library uses a context provider for global React integration. Wrap your app at the highest level:

import { GAProvider } from "@connectaryal/google-analytics";

function App() {
  return (
    <GAProvider config={{ measurementId: "G-XXXXXXXXXX" }}>
      <YourApp />
    </GAProvider>
  );
}
Enter fullscreen mode Exit fullscreen mode

Pro Tip: For Next.js, wrap your custom App or RootLayout component.


Step 4: Track Events: Page Interaction, Engagement, E-commerce

Track a Page View

import { useGoogleAnalytics } from '@connectaryal/google-analytics';

function HomePage() {
  const { trackPage } = useGoogleAnalytics();

  useEffect(() => {
    trackPage(); // auto detects current page
  }, [trackPage]);
}
Enter fullscreen mode Exit fullscreen mode

Custom Engagement & Conversion

import { 
  EventCategory,
  useGoogleAnalytics,
} from '@connectaryal/google-analytics';

function VideoPlayer() {
  const { trackCustomEvent } = useGoogleAnalytics();

  const handleVideoPlay = () => {
    trackCustomEvent({
      name: "video_play",
      category: EventCategory.ENGAGEMENT,
      params: { 
        video_title: "Intro Demo", 
        video_duration: 120 
      },
    });
  };

  return (
    <video onPlay={handleVideoPlay}>
      {/* video content */}
    </video>
  );
}
Enter fullscreen mode Exit fullscreen mode

E-commerce Tracking

import { useGAEcommerce } from "@connectaryal/google-analytics";

function ProductPage() {
  const { trackCart, trackPurchase } = useGAEcommerce();

  const handleAddToCart = () => {
    trackCart({
      action: "add_to_cart",
      items: [{
        item_id: "SKU123", 
        item_name: "Wireless Headphones", 
        price: 99.99, 
        quantity: 1
      }],
      value: 99.99
    });
  };

  const handlePurchase = () => {
    trackPurchase({
      transactionId: "ORDER123",
      value: 199.98,
      items: [{
        item_id: "SKU123", 
        item_name: "Wireless Headphones", 
        price: 99.99, 
        quantity: 2
      }]
    });
  };

  return (
    <div>
      <button onClick={handleAddToCart}>Add to Cart</button>
      <button onClick={handlePurchase}>Buy Now</button>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Step 5: Advanced Tracking: Validated, Typed & Error-Proof

With the GATracker class (used internally, or for advanced users):

  • Every method checks for empty arrays, missing values, or invalid input.
  • All events are type-checked with TypeScript.
  • Debug mode logs warnings and errors clearly.

Example: Handling Validation

const tracker = new GATracker({ 
  measurementId: "G-XXXXXXXXXX", 
  currency: "USD", 
  debug: true 
});

try {
  await tracker.trackCart({
    action: "add_to_cart",
    items: [], // Empty array!
    value: 99.99
  });
} catch (e) {
  // Will throw: "GA4: Cannot track add_to_cart with empty items"
  console.error(e);
}
Enter fullscreen mode Exit fullscreen mode

Step 6: Debug Mode

Turn on debugging to see event logs:

<GAProvider config={{
  measurementId: "G-XXXXXXXXXX",
  debug: process.env.NODE_ENV === "development"
}}>
  <App />
</GAProvider>
Enter fullscreen mode Exit fullscreen mode

Now you'll see every event in the console during development.


Real-World Examples

Complete E-commerce Flow

function ShoppingCart() {
  const { 
    trackCart, 
    trackBeginCheckout, 
    trackShippingInfo, 
    trackPaymentInfo,
    trackPurchase 
  } = useGAEcommerce();

  const [cartItems, setCartItems] = useState([]);
  const [total, setTotal] = useState(0);

  const handleAddToCart = (product) => {
    const newItem = {
      item_id: product.sku,
      item_name: product.name,
      price: product.price,
      quantity: 1
    };

    setCartItems([...cartItems, newItem]);
    setTotal(total + product.price);

    trackCart({
      action: "add_to_cart",
      items: [newItem],
      value: product.price
    });
  };

  const handleCheckout = () => {
    trackBeginCheckout({
      value: total,
      items: cartItems
    });
  };

  const handleShipping = (shippingTier) => {
    trackShippingInfo({
      items: cartItems,
      value: total,
      shipping_tier: shippingTier
    });
  };

  const handlePayment = (paymentMethod) => {
    trackPaymentInfo(
      cartItems,
      total,
      paymentMethod
    );
  };

  const completePurchase = (orderId) => {
    trackPurchase({
      transactionId: orderId,
      value: total,
      items: cartItems
    });
  };

  return (
    <div>
      {/* Cart UI implementation */}
      <button onClick={handleCheckout}>Proceed to Checkout</button>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

User Authentication Tracking

function AuthForm() {
  const { trackLogin, trackSignUp, trackException } = useGoogleAnalytics();

  const handleGoogleLogin = async () => {
    try {
      const user = await signInWithGoogle();

      trackLogin({
        method: "google",
        customParams: {
          user_type: user.isFirstTime ? "new" : "returning"
        }
      });
    } catch (error) {
      trackException({
        description: "Google login failed",
        fatal: false
      });
    }
  };

  const handleEmailSignup = async (email, password) => {
    try {
      await createUserWithEmail(email, password);

      trackSignUp({
        method: "email",
        customParams: {
          signup_source: "landing_page"
        }
      });
    } catch (error) {
      trackException({
        description: "Email signup failed",
        fatal: false
      });
    }
  };

  return (
    <div>
      <button onClick={handleGoogleLogin}>Login with Google</button>
      <form onSubmit={(e) => {
        e.preventDefault();
        handleEmailSignup(email, password);
      }}>
        {/* form fields */}
        <button type="submit">Sign Up</button>
      </form>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Search & Content Engagement

function SearchPage() {
  const { trackSearch, trackShare, trackEngagement } = useGoogleAnalytics();
  const [searchTerm, setSearchTerm] = useState("");
  const [results, setResults] = useState([]);

  const handleSearch = async () => {
    const searchResults = await searchProducts(searchTerm);
    setResults(searchResults);

    trackSearch({
      searchTerm,
      options: {
        search_results: searchResults.length,
        search_category: "products"
      }
    });
  };

  const handleShare = (article) => {
    trackShare({
      contentType: "article",
      contentId: article.id,
      method: "twitter",
      customParams: {
        article_category: article.category
      }
    });
  };

  const handleScroll = (scrollDepth) => {
    if (scrollDepth > 75) {
      trackEngagement({
        type: "scroll",
        options: {
          scroll_depth: scrollDepth,
          page_type: "search_results"
        }
      });
    }
  };

  return (
    <div>
      <input 
        value={searchTerm}
        onChange={(e) => setSearchTerm(e.target.value)}
        onKeyPress={(e) => e.key === 'Enter' && handleSearch()}
      />

      {results.map(result => (
        <div key={result.id}>
          <h3>{result.title}</h3>
          <button onClick={() => handleShare(result)}>Share</button>
        </div>
      ))}
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Why Type Safety Matters

  • No typos: "pageveiw" won't even compile
  • Strict params: keeps you aligned with GA schema
  • Reusable methods: no more raw gtag("event", …) everywhere
  • Validation: automatic parameter validation prevents silent failures
  • IntelliSense: full autocomplete for all GA4 events and parameters

Full API Reference

Here's everything included in the package:

Core Analytics Methods

Method Description
trackPage Page views with automatic path detection
trackCustomEvent Custom events with validation
trackSearch Site search tracking
trackEngagement User engagement (scroll, click, etc.)
trackShare Content sharing events
trackVideoPlay Video interaction tracking
trackFormSubmission Form submission events
trackTiming Performance timing events
trackLogin User authentication
trackSignUp User registration
trackException Error and exception tracking

E-commerce Methods

Method Description
trackItem Item selection/view/list events
trackCart Cart actions (add/remove/view/update)
trackWishlist Wishlist operations
trackBeginCheckout Checkout initiation
trackShippingInfo Shipping information during checkout
trackPaymentInfo Payment method selection
trackPurchase Purchase completion
trackRefund Purchase refunds
trackPromotion Promotion view/select events

Advanced Features

  • Automatic Validation: All methods validate parameters before sending
  • Error Handling: Graceful error handling with detailed error messages
  • Debug Mode: Development-friendly logging and warnings
  • Performance Optimized: Event batching and memory leak prevention
  • TypeScript First: Full type safety with intelligent autocomplete
  • Framework Agnostic: Works with React, Next.js, and other React frameworks

Next.js Integration

App Router (Next.js 13+)

// app/layout.tsx
import { GAProvider } from "@connectaryal/google-analytics";

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body>
        <GAProvider
          config={{
            measurementId: process.env.NEXT_PUBLIC_GA_MEASUREMENT_ID!,
            debug: process.env.NODE_ENV === "development"
          }}
        >
          {children}
        </GAProvider>
      </body>
    </html>
  );
}
Enter fullscreen mode Exit fullscreen mode
// app/components/PageTracker.tsx
"use client";

import { usePathname } from "next/navigation";
import { useGoogleAnalytics } from "@connectaryal/google-analytics";
import { useEffect } from "react";

export function PageTracker() {
  const pathname = usePathname();
  const { trackPage } = useGoogleAnalytics();

  useEffect(() => {
    trackPage({ path: pathname });
  }, [pathname, trackPage]);

  return null;
}
Enter fullscreen mode Exit fullscreen mode

Pages Router (Next.js 12 and below)

// pages/_app.tsx
import type { AppProps } from "next/app";
import { GAProvider, useGoogleAnalytics } from "@connectaryal/google-analytics";
import { useRouter } from "next/router";
import { useEffect } from "react";

function PageTracker() {
  const router = useRouter();
  const { trackPage } = useGoogleAnalytics();

  useEffect(() => {
    const handleRouteChange = (url: string) => {
      trackPage({ path: url });
    };

    router.events.on('routeChangeComplete', handleRouteChange);
    return () => {
      router.events.off('routeChangeComplete', handleRouteChange);
    };
  }, [router.events, trackPage]);

  return null;
}

export default function App({ Component, pageProps }: AppProps) {
  return (
    <GAProvider
      config={{
        measurementId: process.env.NEXT_PUBLIC_GA_MEASUREMENT_ID!,
        debug: process.env.NODE_ENV === "development"
      }}
    >
      <PageTracker />
      <Component {...pageProps} />
    </GAProvider>
  );
}
Enter fullscreen mode Exit fullscreen mode

Advanced Configuration

Environment Variables

# .env.local (Next.js)
NEXT_PUBLIC_GA_MEASUREMENT_ID=G-XXXXXXXXXX

# .env (Create React App)  
REACT_APP_GA_MEASUREMENT_ID=G-XXXXXXXXXX
Enter fullscreen mode Exit fullscreen mode

GDPR/Privacy Compliance

<GAProvider
  config={{
    measurementId: "G-XXXXXXXXXX",
    debug: process.env.NODE_ENV === "development",
    currency: "USD",
    customConfig: {
      // GDPR compliance settings
      analytics_storage: "granted",
      ad_storage: "denied",
      ad_user_data: "denied", 
      ad_personalization: "denied"
    }
  }}
>
  <App />
</GAProvider>
Enter fullscreen mode Exit fullscreen mode

Complete E-commerce Implementation

Product Catalog

function ProductGrid({ products }) {
  const { trackItem } = useGAEcommerce();

  const handleProductView = (product) => {
    trackItem({
      action: "view_item",
      items: [{
        item_id: product.sku,
        item_name: product.name,
        item_category: product.category,
        item_brand: product.brand,
        price: product.price,
        quantity: 1
      }],
      value: product.price,
      customParams: {
        product_position: product.position,
        list_name: "product_catalog"
      }
    });
  };

  return (
    <div className="product-grid">
      {products.map((product, index) => (
        <ProductCard 
          key={product.id}
          product={product}
          onView={() => handleProductView(product)}
        />
      ))}
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Shopping Cart Component

function ShoppingCartPage() {
  const { 
    trackCart, 
    trackBeginCheckout 
  } = useGAEcommerce();

  const { trackCustomEvent } = useGoogleAnalytics();

  const [cart, setCart] = useState([]);

  const updateQuantity = (itemId, newQuantity) => {
    const updatedCart = cart.map(item => 
      item.item_id === itemId 
        ? { ...item, quantity: newQuantity }
        : item
    );
    setCart(updatedCart);

    const updatedItem = updatedCart.find(item => item.item_id === itemId);

    trackCart({
      action: "update_cart",
      items: [updatedItem],
      value: updatedItem.price * updatedItem.quantity
    });
  };

  const removeFromCart = (itemId) => {
    const itemToRemove = cart.find(item => item.item_id === itemId);
    setCart(cart.filter(item => item.item_id !== itemId));

    trackCart({
      action: "remove_from_cart", 
      items: [itemToRemove],
      value: itemToRemove.price * itemToRemove.quantity
    });
  };

  const proceedToCheckout = () => {
    const total = cart.reduce((sum, item) => sum + (item.price * item.quantity), 0);

    trackBeginCheckout({
      value: total,
      items: cart,
      customParams: {
        checkout_step: 1,
        cart_size: cart.length
      }
    });
  };

  return (
    <div>
      {cart.map(item => (
        <div key={item.item_id}>
          <span>{item.item_name}</span>
          <input 
            type="number"
            value={item.quantity}
            onChange={(e) => updateQuantity(item.item_id, parseInt(e.target.value))}
          />
          <button onClick={() => removeFromCart(item.item_id)}>Remove</button>
        </div>
      ))}
      <button onClick={proceedToCheckout}>Checkout</button>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Complete Checkout Flow

function CheckoutProcess() {
  const { 
    trackShippingInfo, 
    trackPaymentInfo, 
    trackPurchase 
  } = useGAEcommerce();

  const [checkoutData, setCheckoutData] = useState({
    items: [],
    total: 0,
    shipping: null,
    payment: null
  });

  const handleShippingSelection = (shippingOption) => {
    setCheckoutData(prev => ({ ...prev, shipping: shippingOption }));

    trackShippingInfo({
      items: checkoutData.items,
      value: checkoutData.total,
      shipping_tier: shippingOption.type,
      customParams: {
        shipping_cost: shippingOption.cost
      }
    });
  };

  const handlePaymentSelection = (paymentMethod) => {
    setCheckoutData(prev => ({ ...prev, payment: paymentMethod }));

    trackPaymentInfo(
      checkoutData.items,
      checkoutData.total + (checkoutData.shipping?.cost || 0),
      paymentMethod.type,
      {
        payment_provider: paymentMethod.provider
      }
    );
  };

  const completePurchase = async () => {
    try {
      const order = await processPayment(checkoutData);

      trackPurchase({
        transactionId: order.id,
        value: order.total,
        items: order.items,
        customParams: {
          affiliation: "Online Store",
          shipping: checkoutData.shipping.cost,
          payment_type: checkoutData.payment.type
        }
      });

      // Redirect to success page
    } catch (error) {
      trackException({
        description: "Purchase failed",
        fatal: false,
        customParams: {
          error_code: error.code
        }
      });
    }
  };

  return (
    <div>
      <ShippingOptions onSelect={handleShippingSelection} />
      <PaymentMethods onSelect={handlePaymentSelection} />
      <button onClick={completePurchase}>Complete Order</button>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Performance & Best Practices

Event Batching

The library automatically batches events to improve performance:

// These events will be batched together
const { trackCustomEvent } = useGoogleAnalytics();

trackCustomEvent({ name: "button_click", params: { button: "header_cta" } });
trackCustomEvent({ name: "section_view", params: { section: "hero" } });
trackCustomEvent({ name: "scroll_depth", params: { depth: 50 } });
Enter fullscreen mode Exit fullscreen mode

Error Handling

function RobustTracking() {
  const { trackCustomEvent, trackException } = useGoogleAnalytics();

  const trackWithErrorHandling = async (eventName, params) => {
    try {
      await trackCustomEvent({ name: eventName, params });
    } catch (error) {
      // Log to your error monitoring service
      console.error("Analytics tracking failed:", error);

      // Optionally track the tracking failure
      trackException({
        description: `Analytics tracking failed: ${eventName}`,
        fatal: false
      });
    }
  };

  return (
    <button onClick={() => trackWithErrorHandling("cta_click", { location: "hero" })}>
      Track Safely
    </button>
  );
}
Enter fullscreen mode Exit fullscreen mode

Testing Your Implementation

Debug Mode Output

When debug mode is enabled, you'll see console output like:

GA4: Initializing with measurement ID: G-XXXXXXXXXX
GA4: Tracking event "page_view" with params: { page_title: "Home", page_location: "/" }
GA4: Event sent successfully ✅
GA4: Tracking event "add_to_cart" with params: { value: 99.99, items: [...] }
GA4: Event sent successfully ✅
Enter fullscreen mode Exit fullscreen mode

Verifying Events in GA4

  1. Go to your GA4 property
  2. Navigate to Reports > Realtime
  3. Trigger events on your site
  4. See events appear in real-time (may take 1-2 minutes)

For detailed event debugging, use Configure > DebugView in GA4.


Comparison with Other Libraries

Feature @connectaryal/google-analytics react-ga4 gtag directly
TypeScript Support ✅ Full ⚠️ Basic ❌ None
E-commerce Events ✅ Complete ⚠️ Limited ⚠️ Manual
Error Handling ✅ Built-in ❌ Manual ❌ Manual
Event Validation ✅ Automatic ❌ None ❌ None
React Hooks ✅ Modern ✅ Basic ❌ None
Debug Mode ✅ Comprehensive ⚠️ Basic ❌ None
Documentation ✅ Complete ⚠️ Basic ❌ Google docs

Why Use Type-Safe Analytics?

  • No more silent failures: Type errors and validation mean you catch mistakes early
  • Enterprise-grade: Handles all GA4 e-commerce, engagement, and conversion flows
  • Debugging is simple: Errors and warnings are clear, actionable, and logged only in dev mode
  • Future-proof: Built with modern React patterns and best practices
  • Maintainable: Clean, readable code that your team can easily understand and extend

Final Thoughts

Modern analytics isn't just about tracking everything—it's about tracking the right things, with clean, validated code you can trust. By starting with a GA4 property and using a type-safe, React-optimized library, your data is actionable and your integration is future-proof.

Ready to track smarter?

Links:

Found an issue or want to contribute? Open an issue on GitHub.

Top comments (1)

Collapse
 
smrpdl1991 profile image
smrpdl1991

Nice package , life saving