In this article, we'll explore a reusable method for integrating analytics into a React application. We'll use Amplitude analytics within a Next.js app as our example, but the same principles can be applied to any React app and analytics provider. All the reusable code for this article can be found in the RadzionKit repository.
In our app, we'll use two main functions to handle analytics:
setUser
- This function sets the user ID for tracking purposes.trackEvent
- This function tracks events. It takes the event name and an optional data object, allowing us to pass any additional information about the event we want to track.import { createContextHook } from "@lib/ui/state/createContextHook"
import { createContext } from "react"
export type AnalyticsContextState = {
setUser: (id: string) => void
trackEvent: (name: string, data?: Record<string, any>) => void
}
export const AnalyticsContext = createContext<
AnalyticsContextState | undefined
>(undefined)
export const useAnalytics = createContextHook(
AnalyticsContext,
"AnalyticsContext"
)
We'll use React's context API to provide these functions to our components. The decision to use the context API instead of utility functions will become clearer when we examine the PageVisitTracker
component. At Increaser, we use this component to track page visits on both the website and the app. The PageVisitTracker
doesn't need to know which analytics tool we're using. It's the responsibility of the app and website projects to wrap the PageVisitTracker
in the appropriate context provider.
import { useAnalytics } from "@lib/analytics-ui/AnalyticsContext"
import { useRouter } from "next/router"
import { useEffect } from "react"
export const PageVisitTracker = () => {
const { pathname } = useRouter()
const { trackEvent } = useAnalytics()
useEffect(() => {
trackEvent("Visit page", { pathname })
}, [trackEvent, pathname])
return null
}
When running the app locally, we don't need to spam our analytics provider with events. For local development, we use the LocalAnalyticsProvider
, which simply logs the events to the console.
import { ComponentWithChildrenProps } from "@lib/ui/props"
import { AnalyticsContext, AnalyticsContextState } from "./AnalyticsContext"
const localAnalytics: AnalyticsContextState = {
setUser: (id) => {
console.log("Set user for analytics: ", id)
},
trackEvent: (name, data) => {
console.log("Track event: ", name, data)
},
}
export const LocalAnalyticsProvider = ({
children,
}: ComponentWithChildrenProps) => {
return (
<AnalyticsContext.Provider value={localAnalytics}>
{children}
</AnalyticsContext.Provider>
)
}
Our AmplitudeAnalyticsProvider
offers the setUser
and trackEvent
functions using the Amplitude analytics library. We initialize the Amplitude library with the provided API key when the component mounts.
import { useEffect } from "react"
import * as amplitude from "@amplitude/analytics-browser"
import { AnalyticsContext, AnalyticsContextState } from "./AnalyticsContext"
import { ComponentWithChildrenProps } from "@lib/ui/props"
type AmplitudeAnalyticsProviderProps = ComponentWithChildrenProps & {
apiKey: string
}
const amplitudeAnalytics: AnalyticsContextState = {
setUser: (id) => {
amplitude.setUserId(id)
},
trackEvent: (name, data) => {
amplitude.track(name, data)
},
}
export const AmplitudeAnalyticsProvider = ({
apiKey,
children,
}: AmplitudeAnalyticsProviderProps) => {
useEffect(() => {
amplitude.init(apiKey)
}, [apiKey])
return (
<AnalyticsContext.Provider value={amplitudeAnalytics}>
{children}
</AnalyticsContext.Provider>
)
}
Now, let's see how we put it all together within our Next.js app. First, we create an AnalyticsProvider
component that checks if the app is running in production. If it is, we use the AmplitudeAnalyticsProvider
with the API key taken from environment variables. Otherwise, we use the LocalAnalyticsProvider
.
import { shouldBeDefined } from "@lib/utils/assert/shouldBeDefined"
import { ComponentWithChildrenProps } from "@lib/ui/props"
import { AmplitudeAnalyticsProvider } from "@lib/analytics-ui/AmplitudeAnalyticsProvider"
import { LocalAnalyticsProvider } from "@lib/analytics-ui/LocalAnalyticsProvider"
export const AnalyticsProvider = ({ children }: ComponentWithChildrenProps) => {
if (process.env.NODE_ENV === "production") {
return (
<AmplitudeAnalyticsProvider
apiKey={shouldBeDefined(process.env.NEXT_PUBLIC_AMPLITUDE_API_KEY)}
>
{children}
</AmplitudeAnalyticsProvider>
)
}
return <LocalAnalyticsProvider>{children}</LocalAnalyticsProvider>
}
Finally in the _app.tsx
file, we wrap our app with the AnalyticsProvider
component and place the PageVisitTracker
component inside it. This way, we can track page visits across the entire app.
import { AnalyticsProvider } from "../analytics/AnalyticsProvider"
import { PageVisitTracker } from "@lib/next-ui/PageVisitTracker"
function MyApp() {
return (
<AnalyticsProvider>
<PageVisitTracker />
// ...
</AnalyticsProvider>
)
}
export default MyApp