0

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>
        );
    }
}
ueeieiie
  • 1,412
  • 2
  • 15
  • 42
  • What is the failure you are experiencing? Are there console errors? – Robert Farley Nov 29 '17 at 13:33
  • @RobertFarley I get no errors. I think the function i pass to the debounce just does not invoke. maybe i didn't write it correctly? – ueeieiie Nov 29 '17 at 13:37
  • 2
    I do not know React and mobx, but: lodash's debounce creates (and returns) a function, hence debounceAction() returns this function, and at the line ("HERE I CALL DEBOUNCE !") the return value of debounceAction() is not used, so nobody actually calls that function. This would call it: `debounceAction(this.buttonHandler, 400)();` - but you are using debounce wrong: you may not call debounce() again and again as in your implementation; you have to call debounce() only once, and then call the _result_ of debounce() again and again. – Oliver Erdmann Nov 29 '17 at 13:42
  • 1
    `return debounce(action(fn), wait)` – Benjamin Gruenbaum Nov 29 '17 at 13:48

1 Answers1

1

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>
Damon
  • 4,216
  • 2
  • 17
  • 27