ueeiee ueeiee - 9 months ago 103
React JSX Question

Why debounce doesnt invoke my function?

Im working on something with React and mobx.

I created an ImageCarousel Component where I show the image that was clicked.
I have a previous and a next buttons(which are themselves a Component), to move between images.

I tried to wrap those actions (prev and next) with

lodash debounce
,
but something fails on the way.

My current store has those actions:


  • prevCarousel

  • nextCarousel

  • debounceAction



debounceAction
is just a Higher Order Function that get 2 parameters (fn, wait), and invoke
lodash debounce
with those parameters.

My CarouselButton component get through its props those actions I mantioned above. inside the Component I trigger with
onClick
event to invoke
debounceAction(fn, wait)
with the actual action (prev, or next).

Im not sure how to wrap my actions with debounce the right way.

I invoke
debounceAction
(the HOF that wraps the debounce) in the second code snippet (in CarouselButton Component).

do you see my mistake here?

galleryStore.jsx - current Store:

class GalleryStore {

// Observables
@observable images = [
// ....images
];

@observable carouselleButtons= {
next: "fa fa-chevron-right",
back: "fa fa-chevron-left"
}
@observable selectedImageIndex = null;
@observable hideCarousel = true;
@observable onTransition = false;

// Actions
selectImage = (index) =>{
this.selectedImageIndex = index;
}

toggleCarousel = () =>{
this.hideCarousel = !this.hideCarousel;
}

carouselNext = () => {
if(this.selectedImageIndex == this.images.length - 1) {
return;
}

this.onTransition = true;
setTimeout(() => {
this.selectedImageIndex = this.selectedImageIndex + 1;
this.onTransition = false;
},500)
}

carouselPrev = () => {
if(this.selectedImageIndex == 0) {
console.log('start of the collection');
return;
}

this.onTransition = true;
setTimeout(() => {
this.selectedImageIndex = this.selectedImageIndex - 1;
this.onTransition = false;
}, 500)
}

debounceAction = (fn, wait) => {
//lodash's debounce
return debounce(fn, wait);
}


carouselButton Component - here I Invoke the debounce:

// React's
import React from 'react';

// Styles
import CarouselButtonStyle from './carouselButtonStyle';

// CarouselButton Component
export default class CarouselButton extends React.Component {
debounce = (e) =>{
const { debounceAction } = this.props;

// -----> HERE I CALL DEBOUNCE ! <---------
e.stopPropagation();
debounceAction(this.buttonHandler, 400);
}

buttonHandler = (e) => {
const {limit, index, action, debounceAction} = this.props;

if(index == limit) return;
else action();
}

render(){
const {limit, index, icon, action, debounceAction} = this.props;

return(
<CarouselButtonStyle
onClick={(e) => {this.debounce(e)}}
className={ index == limit ? 'end-of-collection' : '' } >

<i className={icon} aria-hidden="true" />
</CarouselButtonStyle>
);
}
}


imageCarousel.jsx - carouselButton parent Component:

// React's
import React from 'react';

// Mobx-react's
import { observer, inject } from 'mobx-react';

// Styles
import ImageCarouselStyle from './imageCarouselStyle';

// Components
import ImgContainer from './imgContainer/imgContainer';
import CarouselButton from './carouselButton/carouselButton';

// ImageCarousel Component
@inject('galleryStore')
@observer
export default class ImageCarousel extends React.Component {
closeCarousel = () => {
this.props.galleryStore.toggleCarousel();
}

onKeyDown = (e) => {
const { keyCode } = e;

if(keyCode ===27) this.onEscHandler();
else if (keyCode == 37) this.onLeftArrow();
else if (keyCode == 39) this.onRightArrow();
else return;
}

onLeftArrow = () => { this.props.galleryStore.carouselPrev() }

onRightArrow = () => { this.props.galleryStore.carouselNext() }

onEscHandler = () => { this.closeCarousel() }

componentDidMount(){
document.addEventListener('keydown', this.onKeyDown, false);
}

render(){
return(
<ImageCarouselStyle
hidden={this.props.galleryStore.hideCarousel}
onClick={this.closeCarousel} >

<CarouselButton action={'prev'}
icon={this.props.galleryStore.carouselleButtons.back}
action={this.props.galleryStore.carouselPrev}
limit={0}
index={this.props.galleryStore.selectedImageIndex}
debounceAction={this.props.galleryStore.debounceAction} />

<ImgContainer
images={this.props.galleryStore.images}
imgIndex={this.props.galleryStore.selectedImageIndex}
onTransition={this.props.galleryStore.onTransition}/>

<CarouselButton action={'next'}
icon={this.props.galleryStore.carouselleButtons.next}
action={this.props.galleryStore.carouselNext}
limit={this.props.galleryStore.amountOfImages}
index={this.props.galleryStore.selectedImageIndex}
debounceAction={this.props.galleryStore.debounceAction} />

</ImageCarouselStyle>
);
}
}

Answer Source

The issue is that you have to return debounceAction from CarouselButton's debounce method.

debounce = (e) =>{
    const { debounceAction } = this.props;

    // ----->    HERE I CALL DEBOUNCE !   <---------
    e.stopPropagation();
    debounceAction(this.buttonHandler, 400);
// -^^^ must return here
}

However, I would suggest going a step further to avoid future confusion. Simply invoke lodash's debounce when you need it, rather than rewriting it multiple times in your code and leading to questionable method names.

Here is the most basic example of how you can wrap a click handler:

class Button extends React.Component {
  handleClick = _.debounce((e) => {
    alert('debounced click reaction')
  }, 1000)

  render() {
    return <button onClick={this.handleClick}>CLICK ME</button>
  }
}

ReactDOM.render(
  <Button />,
  document.getElementById('app')
);
<div id="app"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js"></script>

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download