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

Fire track event when element becomes visible #107

Open dhruvg opened 5 years ago

dhruvg commented 5 years ago

First off, thanks for making this. I have been using react-tracking for a few months now and it's so clean while getting me the granularity of tracking I desire. Kudos.

I am curious how users of this library check for component visibility before firing an event. A simple use case is that I have a hidden alert on the page. I want to fire a "show" event when this alert becomes visible (not when it is mounted). This use case is pretty common -- think modals, buttons which are invisible on small screens but become visible on large screens, etc...

So far, the best I have come up with is to use an external library to track component visibility and combine that with react-tracking to fire events then the component becomes visible. But it's clunky. I am envisioning something like this instead:

@track(props => ({ button: props.id, event: 'button.show' }), { dispatchOnVisible: true })
class MyButton extends PureComponent {
  ...
}

Thoughts?

tizmagik commented 5 years ago

Hey @dhruvg thanks for the kudos, that's appreciated and I'm glad you're making good use of react-tracking 😁

So for this use case, you actually have a few options.

Visibility is driven by props

If the visibility of the component is driven by the parent, and thus, by props, then you can actually optionally dispatch a tracking call by returning a falsy value initially and then the object to dispatch when the "show" prop is toggled. Something like:

@track(props => props.hidden ? false : ({ button: props.id }))
class MyButton extends PureComponent { ... }

Although in this case it's probably better to just have the parent not render the component until it should be shown, but it depends where the visibility logic exists.

Visibility is driven by state

If, instead, the component itself manages its own hidden/shown state, then I think you should be able to do something similar:

@track(props => ({ button: props.id }))
class MyButton extends Component {

  // return obj for dispatch while state.hidden is true because we're about to flip it
  @track((props, state) => state.hidden ? ({ event: 'button.show' )} : false) 
  toggleState() { 
    this.setState({ hidden: !this.state.hidden })
  }

  ...
}

Visibility determined by physical visibility in the viewport

This is when an external library, like @rsearchgate/react-intersection-observer (or the straight IntersectionObserver API) can be used.

import React, { Component } from 'react';
import 'intersection-observer'; // optional polyfill
import Observer from '@researchgate/react-intersection-observer';
import track from 'react-tracking';

@track(props => ({ button: props.id })
class MyButton extends Component {

    @track({ event: 'button.show' })
    handleIntersection(event) {
        console.log(event.isIntersecting);
    }

    render() {
        const options = {
            onChange: this.handleIntersection,
            root: '#scrolling-container',
            rootMargin: '0% 0% -25%',
        };

        return (
            <div id="scrolling-container" style={{ overflow: 'scroll', height: 100 }}>
                <Observer {...options}>
                    <div>I am the target element</div>
                </Observer>
            </div>
        );
    }
}

Of course, this would require an IntersectionObserver polyfill for some browsers.


As you can see the use cases for what "visible" means can be very different depending on use case. And even if we can agree that visibility means visible in the viewport, even that can be variable (e.g. how close to the viewport, any buffer? etc). So I hesitate to include this as a built-in feature to react-tracking, but the primitives are there.

Additionally, you could build your own HoC wrapper that encompasses this logic if visibility means the same thing for your app everywhere. Something like a @trackWhenVisible decorator. 😁

Hope that's helpful!

dhruvg commented 5 years ago

Thanks for the in-depth response. I am aiming for the visibility in viewport option. I will give the external lib + HoC a shot.