Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React.js best practice regarding listening to window events from components

I am animating several React.js components based on their position in the viewport. If the component is in the viewport, animate the opacity to 1, if it's not in the viewport, animate its opacity to 0. I am using getBoundingClient()'s top and bottom properties to determine if the component is within the viewport.

ComponentA shows the pattern I followed for the other B, C, and D components. They each are listening for the window scroll event.

Is this the "React" way to do this by each component having its having to add an event listener to the window? Multiple scroll event listeners on the same window?

Or is there a better way by adding the scroll event listener to the window once at the Home owner component? Then would the ownee child components still be able to know where they are in the DOM using the getBoundingClient()?

Home = React.createClass({
 render: function() {
    <div>
       <ComponentA />
       <ComponentB />
       <ComponentC />
       <ComponentD />
    </div>
  };
});

ComponentA = React.createClass({
  componentDidMount: function() {
   window.addEventListener('scroll', this.handleScroll);
},
  componentWillUnmount: function() {
    window.removeEventListener('scroll', this.handleScroll);
   },

handleScroll: function() {
  var domElement = this.refs.domElement.getDOMNode();
  this.inViewPort(domElement);
},

inViewPort: function(element) {
  var elementBounds = element.getBoundingClientRect();
  (elementBounds.top <= 769 && elementBounds.bottom >= 430) ? TweenMax.to(element, 1.5, { opacity: 1 }) : TweenMax.to(element, 1.5, { opacity: 0 });
},
render: function() {
  return (/* html to render */);
 }

});
like image 577
Joe C Avatar asked Oct 01 '15 20:10

Joe C


People also ask

Is it OK to use event listeners in React?

When using React, you generally don't need to call addEventListener to add listeners to a DOM element after it is created. Instead, just provide a listener when the element is initially rendered. You have to be careful about the meaning of this in JSX callbacks. In JavaScript, class methods are not bound by default.

Where do I put window event listener in React?

Here you would add the event listener in by calling window. addEventListener in react's componentDidMount lifecycle method. You would have needed to create a function that will then handle the event and so it is also visible within the other lifecycle methods, such as componentWillUnmount .

How do you handle events in React?

The event handling in react is declarative and the advantage of declarative way to handlers is that they are part of the User interface structure. You cannot return false to prevent default behaviour in React. You must call preventDefault explicitly.

How do you handle multiple events in React?

Call a Function onClick in React The first solution to perform multiple onClick events in React is to include all of your actions inside of a function and then call that single function from the onClick event handler.


4 Answers

There are a few different ways you could do this. One is through composition:

var React = require("react");
var _ = require("underscore");

var ScrollWrapper = React.createClass({
    propTypes: {
        onWindowScroll: React.PropTypes.func
    },

    handleScroll: function(event) {
        // Do something generic, if you have to
        console.log("ScrollWrapper's handleScroll");

        // Call the passed-in prop
        if (this.props.onWindowScroll) this.props.onWindowScroll(event);
    },

    render: function () {
        return this.props.children;
    },

    componentDidMount: function() {
        if (this.props.onWindowScroll) window.addEventListener("scroll", this.handleScroll);
    },

    componentWillUnmount: function() {
        if (this.props.onWindowScroll) window.removeEventListener("scroll", this.handleScroll);
    }
});

var ComponentA = React.createClass({
    handleScroll: function(event) {
        console.log("ComponentA's handleScroll");
    },

    render: function() {
        return (
            <ScrollWrapper onWindowScroll={this.handleScroll}>
                <div>whatever</div>
            </ScrollWrapper>
        );
    }
});

Now, you can place your generic logic in the ScrollWrapper component, and suddenly it becomes reusable. You could create a ComponentB that renders a ScrollWrapper just like ComponentA does.

To satisfy your example, maybe you'll have to pass the ScrollWrapper some extra props from ComponentA. Maybe you'll pass it a prop that contains an instance of the ref to call your logic on. You could even pass it some options or arguments to customize the tween or the bounds. I didn't code any of this because I think you'll understand it and be able to customize/write it for yourself with the base I've provided.

The other way to achieve this sort of thing is through a Mixin. Although, there's a lot of talk about if Mixins are good or bad, and they might even be being deprecated by React in the future? You can do some reading about this and decide for yourself what you think.

like image 109
Kabir Sarin Avatar answered Oct 04 '22 02:10

Kabir Sarin


Here it is in functional style with the useEffect hook:

  useEffect(() => {
    const onScroll = (event) => console.info("scrolling", event);
      
    window.addEventListener('scroll', onScroll);
    
    return () => {
      window.removeEventListener('scroll', onScroll);
    }
  }, []);
like image 40
mtyson Avatar answered Oct 04 '22 01:10

mtyson


Here is a simpler code snippet that should work as required. You are missing the this binding, as such, when you execute window.addEventListener('scroll', this.handleScroll); you are actually pointing this to the window object.

Instead you will need to bind this in the constructor. Hope it

class Home extends Component {
  constructor(props) {
    super(props)
    this.handleScroll = this.handleScroll.bind(this);
  }
  componentDidMount() {
    window.addEventListener('scroll', this.handleScroll);
  }
  componentWillUnmount() {
    window.removeEventListener('scroll', this.handleScroll);
  }
  handleScroll(e) {
    console.log('scroll event');
    console.log(e);
  }

  render() {
    return (
     <div>
       <ComponentA />
       <ComponentB />
       <ComponentC />
       <ComponentD />
     </div>
    );
  }
}

Another option is the below, both options should work :)

class Home extends Component {
  componentDidMount() {
    window.addEventListener('scroll', this.handleScroll.bind(this));
  }
  componentWillUnmount() {
    window.removeEventListener('scroll', this.handleScroll.bind(this));
  }
  handleScroll(e) {
    console.log('scroll event');
    console.log(e);
  }

  render() {
    return (
     <div>
       <ComponentA />
       <ComponentB />
       <ComponentC />
       <ComponentD />
     </div>
    );
  }
}
like image 35
MCSLI Avatar answered Oct 04 '22 02:10

MCSLI


I'd definitely add one event listener / component. The ideology is to have separated components that can be reused and placed "anywhere" in the application - to minimize code redundancy.

Your approach to keep an event listner per compnenent is therefore valid.

like image 23
Eric Avatar answered Oct 04 '22 00:10

Eric