React.js best practice regarding listening to window events from components
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);
}
}, []);
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>
);
}
}
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.