Skip to content

Instantly share code, notes, and snippets.

@kant01ne
Created October 21, 2022 10:17
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kant01ne/639f3539f24c89f8573f5eda729e38e2 to your computer and use it in GitHub Desktop.
Save kant01ne/639f3539f24c89f8573f5eda729e38e2 to your computer and use it in GitHub Desktop.
import React from 'react'
// We use lodash to transform the label name, if you don't have lodash, you could either install it, remove that part, or implement your own version of camel case.
import camelCase from 'lodash/camelCase'
export type AnalyticsListenerContextValue = {
// When manually calling stopPropagation, we want to make sure we still capture analytics so prefer using stopPropagation from this provider instead of calling event.stopPropagation directly.
stopPropagation: (
event:
| React.KeyboardEvent
| MouseEvent
| React.MouseEvent<HTMLElement, MouseEvent>
) => void
trackEvent: (event: string, properties?: Record<string, unknown>) => void
}
const AnalyticsListenerContext =
React.createContext<AnalyticsListenerContextValue>({
stopPropagation: () => {},
trackEvent: () => {},
})
export const AnalyticsListenerProvider: React.FC<{
children: React.ReactNode
track?: (name: string, properties?: Record<string, unknown>) => void
}> = ({ children, track }) => {
const processEvent: AnalyticsListenerContextValue['stopPropagation'] =
React.useCallback(
(event) => {
if (!event || !event.target) {
return
}
const target = event.target as Element
const attr = getTargetAttr(target)
if (attr) {
track?.('click', attr)
}
},
[track]
)
React.useEffect(() => {
document.addEventListener('click', processEvent)
return () => {
document.removeEventListener('click', processEvent)
}
}, [processEvent])
const trackEvent = React.useCallback(
(event: string, properties?: Record<string, unknown>) => {
track?.(event, properties)
},
[track]
)
const stopPropagation: AnalyticsListenerContextValue['stopPropagation'] =
React.useCallback(
(event) => {
processEvent(event)
event.preventDefault()
event.stopPropagation()
},
[processEvent]
)
return (
<AnalyticsListenerContext.Provider value={{ stopPropagation, trackEvent }}>
{children}
</AnalyticsListenerContext.Provider>
)
}
const getTargetAttr = (
target: Element,
depth = 3
): { label: string; type: string } | undefined => {
let label = ''
let type: string
if (target.getAttribute('data-analytics-label')) {
label =
target.getAttribute('data-analytics-label') ||
target.ariaLabel ||
(target as HTMLButtonElement).innerText
type = target.nodeName.toLowerCase()
return {
label: camelCase(label),
type,
}
}
switch (target.nodeName) {
case 'BUTTON': {
label =
target.getAttribute('data-analytics-label') ||
target.ariaLabel ||
(target as HTMLButtonElement).innerText
type = 'button'
break
}
case 'SELECT': {
label = target.getAttribute('data-analytics-label') || target.ariaLabel
type = 'select'
break
}
case 'A': {
label =
target.getAttribute('data-analytics-label') ||
target.ariaLabel ||
(target as HTMLButtonElement).innerText
type = 'a'
break
}
case 'INPUT': {
if ((target as HTMLInputElement).type !== 'checkbox') {
return undefined
}
label = target.ariaLabel || (target as HTMLButtonElement).innerText
type = 'checkbox'
break
}
default: {
return depth === 0 || target.parentElement === null
? undefined
: getTargetAttr(target.parentElement, depth - 1)
}
}
return {
label: camelCase(label),
type,
}
}
export const useAnalyticsListener = (): AnalyticsListenerContextValue =>
React.useContext(AnalyticsListenerContext)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment