React — catch wheel event on overflow:hidden element? - reactjs

How can I catch a scroll / wheel event in a React component if the element has overflow: hidden?
Something like this:
<Component onWheel={this.handleWheel.bind(this)}/>

So I found your question since I was running on a similar issue. My problem was that my div was not scrollable and I needed to capture the scroll event or the wheel move really. This what I did:
export default class MyComponent extends Component {
constructor(){
super();
}
handleScroll(e) {
console.log('Scroll event is being called', e);
}
componentDidMount() {
const holder = ReactDOM.findDOMNode(this.refs.holder)
holder.addEventListener('mousewheel', this.handleScroll);
}
componentWillUnmount() {
const holder = ReactDOM.findDOMNode(this.refs.holder)
holder.removeEventListener('mousewheel', this.handleScroll);
}
render() {
const hiddenRecipients = amountOfRecipients - children.length;
return (
<div className="my_holder" ref="holder" />)
}
}
So basically I just added a ref and attached an event listener to the mouse wheel.
Hope it helps

const onWheel = e => {
e.preventDefault()
console.log(e.deltaY)
}
<Component onWheel={onWheel} />
It's just another way to handle (catch) wheel data in react

Related

How do you use the Swiper callback methods in React?

I have a component class and need to be able to use .slideToLoop() and .update(), and haven't been able to figure out how to use those methods with the Swiper React library.
I need to do this when something else is clicked on, so that the Swiper can update (as it's hidden initially), and then slideTo the relevant slide.
At the moment, I have the click trigger in jQuery in componentDidMount() as I'm porting things over into React. But happy to change this as well if it's better to. The click happens on a grandchild component.
And I have the swiper instance being set into the state, but that happens after componentDidMount, so I can't access it from there.
Code:
constructor(props) {
super(props);
this.state = {
swiperIns: ''
}
}
setSwiper = (swiper) => {
this.setState({swiperIns: swiper}, () => {
console.log(this.state.swiperIns);
});
}
componentDidMount() {
const { appStore } = this.props;
if (!appStore.hasLoadedHomePage)
appStore.loadHomePage().catch((ex) => null);
const mySwiper = this.state.swiperIns;
console.log(mySwiper); // returns ''
$('.modal-trigger').on('click', function(e) {
e.preventDefault();
var modalToOpen = $(this).attr('data-modal-id');
console.log(modalToOpen);
if ($(this)[0].hasAttribute('data-slideindex')) {
const slideTo = parseInt($(this).attr('data-slideindex'));
// this.state.slideToLoop(slideTo);
}
$('#' + modalToOpen).show();
$('body').addClass('modal-open');
if ($('#' + modalToOpen).hasClass('modal--swiper')) {
// this.state.swiperIns.update();
}
});
}
and the return part of the Swiper:
<Swiper onSwiper={ this.setSwiper } spaceBetween={0} slidesPerView={1} loop>
...
</Swiper>
Any help or suggestions are appreciated.
Okay, so I figured it out.
First, you add a ref to the constructor:
constructor(props) {
super(props);
this.swiperRef = React.createRef();
}
And in componentDidMount, like so:
const mySwiper = this.swiperRef;
And then on the Swiper element, you set the ref to the Swiper instance like so:
<Swiper ref={ this.swiperRef }...</Swiper>
And then in button clicks/functions, you can use this.swiperRef.current?.swiper.slideNext(); or any other Swiper callback methods to update/slideTo/etc.

React - get element.getBoundingClientRect() after window resize

I have a class that needs to get the size of a DOM element. It works well, but when I resize the window it doesn't update until I change the state in my app, forcing a rerender. I've tried adding this.forceUpdate to a 'resize' event listener in componentDidMount(), but it didn't work. Perhaps I did something wrong? Ideally I'd like to avoid using this.forceUpdate for perfomance implications anyway. Any work arounds for this? Thanks in advance!
My code:
class MyComponent extends React.Component {
state = { x: 0, y: 0 }
refCallback = (element) => {
if (!element) {
return
}
const { x, y } = element.getBoundingClientRect()
this.setState({ x, y })
}
render() {
console.log('STATE:', this.state) // Outputs the correct x and y values.
return (
<div ref={this.refCallback}>
<button>Hello world</button>
</div>
)
}
}
If you want to measure some element in your component whenever the window resizes, it's going to look something like this:
class MyComponent extends React.Component {
state = {
x: 0,
y: 0,
};
element = React.createRef();
onWindowResize = () => {
if (this.element.current) {
const {x, y} = this.element.current.getBoundingClientRect();
this.setState({x, y}, () => {
console.log(this.state);
});
}
};
componentDidMount() {
window.addEventListener('resize', this.onWindowResize);
}
componentWillUnmount() {
window.removeEventListener('resize', this.onWindowResize);
}
render() {
return (
<div ref={this.element}>
<button>Hello, World</button>
</div>
);
}
}
The trick here is that your ref callback is only called once, when the element is initially added to the DOM. If you want to update state whenever you resize the window, you're going to need a 'resize' event handler.
That happening because:
From the React documentation:
Adding a Ref to a DOM Element
React supports a special attribute that you can attach to any component. The ref attribute takes a callback function, and the callback will be executed immediately after the component is mounted or unmounted.
React will call the ref callback with the DOM element when the component mounts, and call it with null when it unmounts.
So, that's why when you refresh you get the value. To overcome the problem you can do something like this:
import React from "react";
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.myRef = React.createRef();
this.state = {
x: 0,
y: 0
};
}
updateDimensions = () => {
if (this.myRef.current) {
const {x, y} = this.myRef.current.getBoundingClientRect();
this.setState({ x, y });
}
};
componentDidMount() {
window.addEventListener("resize", this.updateDimensions);
}
componentWillUnmount() {
window.removeEventListener("resize", this.updateDimensions);
}
render() {
console.log("STATE:", this.state); // Outputs the correct x and y values.
return (
<div ref={this.myRef}>
<button>Hello world</button>
</div>
);
}
}
export default MyComponent;
Hope this works for you.

Calling addEventListener after setState seems to run event twice

If I add an event listener after calling setState, I would expect that event listener to only get called if I trigger that event again. However, from the example below, when I click on the div, the event listener is called after the state is changed.
It doesn't happen if I remove the addEventListener or if I call evt.stopPropagation() inside toggleOpen, but I'm wondering why the event listener is getting called if I'm setting it after thte state is changed.
Doesn't setState change the state asynchronously, implying that the callback would be called after the event propagates?
import React from 'react';
import classNames from 'classnames';
import css from './Dropdown.scss';
export class Dropdown extends React.Component {
constructor(props) {
super(props);
this.state = {
open: false,
}
}
toggleOpen = (evt) => {
window.removeEventListener('click', this.toggleOpen);
this.setState({
open: !this.state.open,
}, () => {
window.addEventListener('click', this.toggleOpen);
});
}
render() {
const dropdownContentClasses = classNames(css.dropdownContent, {
[css.dropdownContent_open]: this.state.open,
});
console.log(this.state.open)
return (
<div className={css.dropdownContainer}>
<div onClick={this.toggleOpen}>
{this.props.title}
</div>
<div className={dropdownContentClasses}>
{this.props.children}
</div>
</div>
)
}
}
2 things. First, from https://reactjs.org/docs/react-component.html#setstate you want to use the prevState property to calculate your new state:
toggleOpen = evt => {
this.setState((prevState, props) => {
return { open : ! prevState.open };
});
}
Second, it's better to add your event handler during the componentDidMount() phase, and remove it during componentWillUnmount() phase:
componentDidMount() {
window.addEventListener('click', this.toggleOpen);
}
componentWillUnmount() {
window.removeEventListener('click', this.toggleOpen);
}
It seems like by the time the propagation reaches the window, the listener is added and then calls the eventListener. Adding event.stopPropagation should solve it.
This is weird!
Adding event listener on document instead of window fixes the issue.
https://jsfiddle.net/y09h3nuf/
Instead of adding the window event on toggleOpen, i would suggest to add the event inside componentDidMount with a state.open check.
componentDidMount() {
window.addEventListener('click', (e) => {
this.state.open && this.toggleOpen(e);
});
}
I added a fiddle here: https://jsfiddle.net/69z2wepo/87489/

Correct way to get refs and attach event listeners in React ComponentDidMount?

The problem is roughly summarized in the comments in the code snippet. When I bind this._setSize in constructor, it never knows about this.container — even when called in componentDidMount. What am I doing wrong? Thanks!
export default class MyComponent extends React.Component {
constructor () {
super()
this._setSize = this._setSize.bind(this)
}
componentDidMount () {
const container = this.container // <div></div> :)
this._setSize()
window.addEventListener('resize', this._setSize)
}
componentWillUnmount () {
window.addEventListener('resize', this._setSize)
}
_setSize () {
const container = this.container // undefined :(
const containerSize = {
x: container.offsetWidth,
y: container.offsetHeight
}
this.setState({ containerSize })
}
render () {
return (
<div ref={node => this.container = node}>
</div>
)
}
}
Within each re-render you are creating and passing new instance of function to setup container ref. The previous function is then called with null. Therefore it might happen that you accidently set this.container to null:
<div ref={node => this.container = node}>
When you pass here component instance method instead of inline function, it is called once with reference and second time with null during component unmount. E.g.:
// dont forget to bind it in constructor
onContainerRef (node) {
// furthermore you can even check if node is not null
// if (node) { ...
this.container = node
}
// ... in render
<div ref={this.onContainerRef}>
You can read more in docs.
I fixed your code and it's working now: see working DEMO
What was the problem?
componentWillUnmount () {
window.addEventListener('resize', this._setSize)
}
You didn't remove event listener from window because in componentWillUnmount you have addEventListener instead of removeEventListener. If you have any conditional rendering of the component, on resize event _setSize will be also called.
To illustrate this problem, play with the broken demo and click on Toggle button and look at output: see broken DEMO

React onScroll not working

I'm trying to add onscroll event handler to specific dom element. Look at this code:
class ScrollingApp extends React.Component {
...
_handleScroll(ev) {
console.log("Scrolling!");
}
componentDidMount() {
this.refs.list.addEventListener('scroll', this._handleScroll);
}
componentWillUnmount() {
this.refs.list.removeEventListener('scroll', this._handleScroll);
}
render() {
return (
<div ref="list">
{
this.props.items.map( (item) => {
return (<Item item={item} />);
})
}
</div>
);
}
}
Code is quite simple, and as you can see, I want to handle div#list scrolling. When I was running this example, it isn't working. So I tried bind this._handleScroll on render method directly, it doesn't work either.
<div ref="list" onScroll={this._handleScroll.bind(this)> ... </div>
So i opened chrome inspector, adding onscroll event directly with:
document.getElementById('#list').addEventListener('scroll', ...);
and it is working! I don't know why this happens. Is this a bug of React or something? or did I missed something? Any device will very appreciated.
The root of the problem is that this.refs.list is a React component, not a DOM node. To get the DOM element, which has the addEventListener() method, you need to call ReactDOM.findDOMNode():
class ScrollingApp extends React.Component {
_handleScroll(ev) {
console.log("Scrolling!");
}
componentDidMount() {
const list = ReactDOM.findDOMNode(this.refs.list)
list.addEventListener('scroll', this._handleScroll);
}
componentWillUnmount() {
const list = ReactDOM.findDOMNode(this.refs.list)
list.removeEventListener('scroll', this._handleScroll);
}
/* .... */
}

Resources