nytimes / react-tracking

🎯 Declarative tracking for React apps.
https://open.nytimes.com/introducing-react-tracking-declarative-tracking-for-react-apps-2c76706bb79a
Other
1.88k stars 123 forks source link

Track Hierarchy #93

Closed jbadeau closed 6 years ago

jbadeau commented 6 years ago

Hi,

Given: a tracked component tree like:

App @track({app:'myApp'})
|--Page @track({page:'page1'})
    |-TabBar @track({tabbar:'tabbar1'})
       |-Tab @track({tab:'tab1'})
       |-Tab @track({tab:'tab2'})

When: the user selects a Tab1

Then: I would like to be able to dispatch a message like: {event: 'myapp.page1.tabbar1.tab1'}

The current dispatch method provides an object like:

{app:'myApp', 'tab': 'tab1', page: 'myApp', tabBar:'tabBar1'}

but I don't know the hierarchy. Is there some way I can create the hierarchy in the dispatch or pre-process

Regards Jose

daniel-winter commented 6 years ago

Take a look here to the answer of @tizmagik https://github.com/NYTimes/react-tracking/issues/85#issuecomment-417080034

jbadeau commented 6 years ago

Hi,

My issue is not to know which tab was clicked but to be able to reconstruct the component hierarchy. TrackingData is an object which means I cant' rely on property iteration order to reconstruct the tree.

tizmagik commented 6 years ago

Hey @jbadeau you should be able to construct this hierarchy in a custom dispatch() method you define. Something like this could work:

const dispatch = data => {
  const { app, page, tabBar, tab, ...rest } = data;
  const hierarchy = [
    app,
    page,
    tabBar,
    tab
  ].filter(Boolean).join('.');

  window.dataLayer.push({
    hierarchy,
    ...rest // if you want the rest of the data
  });
};

But this kind of assumes you know the hiearchy implicitly at the dispatch level so that you can reconstruct it. I'm not sure if there's a way to programmatically generate this hierarchy, but the approximation created by filtering out falsey values filter(Boolean) might be good enough (e.g. you probably won't have app a descendent of tabBar, for example).

Does this help solve your use case, or is there more to it?

jbadeau commented 6 years ago

Exactly, I don't know the order. Humm, I will see what I can come up with in.

jbadeau commented 6 years ago

We have forked the project. The issues is that if a child component tracks a property with the same name as a parent component, then it is replaced via the deep merge.

deep merge:

parent {foo: {a:1}} child {foo: 'dog'} merged obj {foo: 'dog'}

shallow append

parent {foo: 'cat'} child {foo: 'dog'} merged obj {foo: [{a:1}, 'dog']}

the properties are merged into any array in the order of the component hierarchy. we don't want to loose properties. Developers can still merge if they want to keep the existing behavior

Cheers, Jose

tizmagik commented 6 years ago

Interesting. Might be useful as an optional property to change the merge behavior at some root-level component, similar to how dispatch behavior is defined:

@track({}, { dispatch, shallowAppend: true })
export default class App extends Component { ... }

@jbadeau if you want to contribute that back instead of maintaining a separate fork I think we can consider it here, it seems generally useful.

Another approach might be to leave the merge function up to userland. By default it would do a deep-merge, but a shallowAppend data could be provided if it makes more sense for your app, for example:

@track({}, { dispatch, merge: (contextData, ownData) => shallowAppend(contextData, ownData) })
export default class App extends Component { ... }
jbadeau commented 6 years ago

I def like the idea of a user configurable merge strategy. Let me refactor the code a bit to try that. As the code is maintained in a private git repo I cannot share but I can provide a git patch