Skip to content
This repository has been archived by the owner on May 6, 2024. It is now read-only.

Latest commit

 

History

History
228 lines (193 loc) · 4.8 KB

MOTIVATION.md

File metadata and controls

228 lines (193 loc) · 4.8 KB

Motivation

The larger a web applications grows, the harder it is to provide predictable and traceable tracking structures. Consider our usual analytics event dispatching:

import React, { useContext } from 'react';
import { TrackingContext } from 'your-tracking';

function Button({ onClick, label, category, value, dispatch, children }) {
  let handler = onClick;

  if (dispatch) {
    handler = e => {
      dispatch({ label, category, value });
      onClick && onClick(e);
    };
  }

  return <button onClick={handler}>{children}</button>;
}

function AccountBalance() {
  return (
    <Button
      type="submit"
      label="show-value"
      category="balance"
      dispatch={dispatch}
    >
      Click me
    </Button>
  );
}

function AccountPage() {
  return (
    ...,
    <Balance />
  );
}

function Balance() {
  return (
   ...,
   <ShowBalance />
  );
}

function App() {
  return (
    <TrackingContext.Provider
      value={{
        dispatch: e => {
          window.dataLayer.push(e);
        }
      }}
    >
      <AccountPage />
    </TrackingContext.Provider>
  );
}

Now, what happens if we need to define the event category somewhere else in the tree? For this small example it might not sound like much:

function AccountBalance({ category }) {
  const { dispatch } = useContext(TrackingContext);

  return (
    <Button
      type="submit"
      label="show-value"
      category={category}
      dispatch={dispatch}
    >
      Click me
    </Button>
  );
}

But over time this can tightly couple our component implementation with the tracking usage. Ideally the AccountBalance component shouldn't have to worry about this sort of domain.

What about leveraging our already existing TrackingContext?

function AccountBalance() {
  const { dispatch, category } = useContext(TrackingContext);

  return (
    <Button
      type="submit"
      label="show-value"
      category={category}
      dispatch={dispatch}
    >
      Click me
    </Button>
  );
}

But having a context usage also implies that you eventually need to set the value somewhere in the tree:

function AccountBalance() {
  const { dispatch, category } = useContext(TrackingContext);

  return (
    <Button
      type="submit"
      label="show-value"
      category={category}
      dispatch={dispatch}
    >
      Click me
    </Button>
  );
}

function Balance() {
  const { dispatch, setValue } = useContext(TrackingContext);
  useEffect(() => {
    setValue({ category: 'account-balance' });
  }, []);

  return (
   ...,
   <ShowBalance />
  );
}

function App() {
  const [trackingData, setTrackingData] = useState({});
  return (
    <TrackingContext.Provider
      value={{
        ...trackingData,
        dispatch: e => {
          window.dataLayer.push(e);
        },
        setValue: value => setTrackingData({ ...trackingData, ...value })
      }}
    >
      <AccountPage />
    </TrackingContext.Provider>
  );
}

But again, over time this can tightly couple our component implementation with the event tracking usage, and the more fields you need to overwrite, the harder it is to reason about the current state of the context. That's where Collector can help you!

Collector was built to track user-interactions with high granularity. Using an agnostic event schema you can serve different tracking purposes with it. Consider the same example using Collector:

import React from 'react';
import {
  TrackingRoot,
  TrackingView,
  TrackingElement,
  useClickTrigger
} from '@sumup/collector';

function Button({ onClick, 'tracking-label': trackingId, children }) {
  const dispatch = useClickTrigger();
  let handler = onClick;

  if (trackingId) {
    handler = (e) => {
      dispatch({ label: trackingId, component: 'button' });
      onClick && onClick(e);
    };
  }

  return <button onClick={handler}>{children}</button>;
}

function AccountBalance() {
  return (
    <Button type="submit" tracking-label="show-balance">
      Click me
    </Button>
  );
}

function AccountPage() {
  return (
    <TrackingView name="account">
      ...,
      <Balance />
    </TrackingView>
  );
}

function Balance() {
  return (
    <TrackingElement name="balance">
      ...,
      <ShowBalance />
    </TrackingElement>
  );
}

function toAnalyticsEvent({ view, elementTree, label, action }) {
  return {
    category: `${view}-${elementTree.join('-')}`,
    label: label,
    action
  };
}

function App() {
  return (
    <TrackingRoot
      name="my-app"
      onDispatch={(event) => {
        window.dataLayer.push(toAnalyticsEvent(event));
      }}
    >
      <AccountPage />
    </TrackingRoot>
  );
}

For more information about the event schema and component structure, please refer to the Event Schema and Usage sections.