Expo

Use onedollarstats/expo to track screen views and custom events in apps that use Expo Router. The same integration works on iOS, Android and the web. Screen views appear as page views in OneDollarStats.

Requirements: expo-router 3 or later, react-native 0.73 or later, and React 18 or later.

Installation

In your Expo project, install the package:

npm
yarn
pnpm
bun
npm i onedollarstats

Setup

Add a site in OneDollarStats, or use an existing site. Set hostname to that site’s domain. This required value determines where the app’s events are recorded: a native app has no domain of its own.

In app/_layout.tsx, wrap your root layout in OneDollarStatsProvider. Replace mydomain.com with your site’s domain:

import { Stack } from "expo-router";
import { OneDollarStatsProvider } from "onedollarstats/expo";

export default function RootLayout() {
  return (
    <OneDollarStatsProvider config={{ hostname: "mydomain.com" }}>
      <Stack />
    </OneDollarStatsProvider>
  );
}

Screen paths are added to hostname. For example, the /settings screen is recorded as mydomain.com/settings.

Dev mode

By default, development builds do not send events or page views. This includes Expo Go and apps run with npx expo start on iOS, Android and the web. Expo web apps served from localhost are also excluded.

To test tracking during development, add devmode: true to the provider’s config:

<OneDollarStatsProvider config={{ hostname: "mydomain.com", devmode: true }}>
  <Stack />
</OneDollarStatsProvider>

Start the app and navigate between screens. The console shows a connection message and logs each event sent. To see these debug events in your dashboard, open your site in OneDollarStats and enable Dev Mode. See Dev mode for details.

In release builds, devmode is ignored and events are sent as production traffic.

What is tracked automatically

With autocollect: true, the default, the tracker sends page views for:

By default, dynamic routes use their file names as templates: /profile/abc123 is sent as /profile/[id]. This groups all profiles under one page. Route groups are removed, so app/(tabs)/feed.tsx is sent as /feed.

Re-rendering a screen does not send another page view. Neither does moving between screens with the same tracked path, such as /profile/a and /profile/b when dynamic routes are grouped. See Dynamic routes to track each profile separately.

Send events and page views manually

Call useAnalytics() in a component inside OneDollarStatsProvider. The hook returns event() for custom events and view() for page views:

import { Button } from "react-native";
import { useAnalytics } from "onedollarstats/expo";

export default function Pricing() {
  const { event } = useAnalytics();

  return (
    <Button title="Buy" onPress={() => event("Buy click", { plan: "pro" })} />
  );
}

Both functions accept an optional path and custom properties, as in the NPM package. Without a path, they use the current screen’s tracked path. Property values must be strings.

The following calls show the argument combinations. Obtain event and view from useAnalytics() inside your component:

const { event, view } = useAnalytics();

// Custom events
event("Buy click");
event("Buy click", "/product");
event("Buy click", { plan: "pro" });
event("Buy click", "/product", { plan: "pro" });

// Page views
view();
view("/landing");
view({ campaign: "spring" });
view("/landing", { campaign: "spring" });
IMPORTANT

view() skips a page view if its path matches the last page view sent, whether automatic or manual. For example, view({ campaign: "spring" }) sends nothing if automatic tracking has just counted the current screen.

Consecutive calls for the same tracked path are skipped even with autocollect: false. Custom events are not deduplicated.

Paths are normalized before sending and comparison: "/landing", "landing/" and "/landing?ref=ad" all become /landing. Query strings, URL fragments and trailing slashes are removed.

Dynamic routes

By default, app/profile/[id].tsx is tracked as /profile/[id], regardless of the profile ID. To track each profile by its actual path, such as /profile/abc123, set collapseDynamicRoutes: false:

<OneDollarStatsProvider
  config={{ hostname: "mydomain.com", collapseDynamicRoutes: false }}
>
  <Stack />
</OneDollarStatsProvider>

Include or exclude screens

Use excludePages to skip screens during automatic tracking. This example skips /settings, /admin and all routes under either path:

<OneDollarStatsProvider
  config={{
    hostname: "mydomain.com",
    excludePages: ["/settings", "/admin"],
  }}
>
  <Stack />
</OneDollarStatsProvider>

Use includePages to track only selected screens. This example tracks /onboarding and all routes under it:

<OneDollarStatsProvider
  config={{ hostname: "mydomain.com", includePages: ["/onboarding"] }}
>
  <Stack />
</OneDollarStatsProvider>

Each entry matches a path and its subpaths. For example, "/admin" matches /admin and /admin/users, but not /administrator. Wildcards are not supported.

The lists have these rules:

Manual view() and event() calls ignore both lists.

Devices, browsers and OS

For iOS and Android apps, the dashboard shows the operating system and device type (phone or tablet). Native apps do not report a browser. Expo web reports the same device, browser and OS information as other websites. See Devices, browsers and OS.

Configuration

OptionTypeDefaultDescription
hostnamestring—Required. Domain under which the app’s events are recorded
collectorUrlstringhttps://collector.onedollarstats.com/eventsURL to send analytics events
autocollectbooleantrueTrack the first screen, screen changes and returns from the background as page views
devmodebooleanfalseSend development events as debug events; ignored in release builds
collapseDynamicRoutesbooleantrueSend dynamic routes as templates (/profile/[id])
excludePagesstring[]—Paths and subpaths to skip during automatic tracking
includePagesstring[]—Track only these paths and subpaths; overrides excludePages when nonempty

Differences from the web tracker